Tutorial Writing a large plugin: An MVC approach

Discussion in 'Resources' started by SOFe, Aug 13, 2017.

  1. SOFe

    SOFe Administrator Staff Member PMMP Team Poggit Admin Noobiest member in the PMMP Team

    Messages:
    1,760
    GitHub:
    sof3
    This tutorial is for real large plugins. By "real" I mean that it is really a single plugin, not a big dish of spaghetti with many small plugins mixed together (like EssentialsPE).

    In this tutorial I'll be taking a Factions plugin for example. A Factions plugin is very complex, ranging from data saving to action checking to various commands. So, where do we start at?

    The first step is to write a plugin proposal. A proposal is not simply like "I want a Factions plugin". A good proposal should go through all details, such as:
    1. What is the plugin used for? You may find this quite irrelevant, but for high-quality and complex plugins, making it clear is really important. For example, for the Factions plugin, do you simply want players to get assigned into teams? Or do you want to use it as a means of land management? If so, land management for what? Will it be sustainable? Can new players have a chance to replace old inactive players? Getting this clear is very important for defining fine semantics of your plugin, as well as deciding what features to include. There are many edge cases that a careful developer (especially one with OCD) would consider when writing the actual code, and getting your aim clear can help you maintain consistency in your plugin. This may also inspire you to add some useful configuration values to allow diff uses.
    2. The technical details of your plugin. For example, where are the data saved? What changes do the players/owner see? What commands are available, and what do they do exactly?
    3. Is your plugin even feasible? Explain how you'd make the features possible. For example, a feature might be "Show your faction's claimed area by placing blocks at the sky level". But, will it be laggy? Will it even be visible? Will it block the sky light? These are all obvious technical difficulties that you'd have to consider carefully.
    4. For public plugins: Is the plugin too specific? Think about what you would hardcode. This is not limited to strings (like translations) and numbers (like the price of claiming land). This also includes complex values, such as the relationship between different factions (ally? Truce? Enemy?) and the ranks and permissions of a faction member. In addition to per-server configuration, also consider adding per-player configuration.
    5. The administrative stuff, such as the plugin name, plugin license, copyright ownership, source control system (Git or other source code versioning tools? How do you manage collaboration and branching?) etc. This sounds quite minor, but these are things that you must decide before starting your work. Administrative distraction is the nightmare of every enthusiastic programmer because it effectively winds down all your enthusiasm, so get them done ASAP. In addition, changing these after starting the project is usually much more troublesome.
    6. What plugins will your plugin work with? For example, will a land protection plugin break sign-clicking plugins? Can a factions plugin integrate with an economy plugin? Think about both the trouble and the convenience -- you may break some plugins, but you may also make some plugins more useful together!
    Such a project proposal can particularly help your cooperate with collaborators when defining edge cases if you're not alone. But even if you are alone, you have to cooperate with yourself 3 months later too.

    This is just step one for the development. If you can't even write a detailed proposal, it is impossible to be able to write the plugin. Of course the proposal may be changed after you started working, but the proposal itself is also very important to prevent starting wrongly, leading to the need of rewriting many times.

    With the proposal, you can start writing your plugin. But you still need to do some preparation before writing the code:
    1. Set up your development environment and deployment (testing) environment. Set up continuous integration (e.g. Poggit, Travis) for your Git repo (if any). Don't end up finding out that you can't test conveniently because you only use DevTools and you can't test without copying your code to the plugins folder and end up breaking the IDE because your project path changed.
    2. Look for useful libraries. You may want to use the libraries to reduce code to write and concentrate on your plugin. You may find popular virions (libraries for PocketMine plugins) on Poggit (link coming soon). (See virion documentation at https://poggit.github.io/support/virion.html).
    And finally we get to the actual code.
    A large plugin comprises a lot of complex mechanism, and it is really hard to find where to start with. My suggestion is to start with the data structures (after you completed the basic stuff of creating plugin.yml and the main class). For example, a factions plugin may start with creating the Faction class that defines the properties of a faction (faction name, faction type, internal ranks). Then you may extend to other data structure classes like FactionRank, FactionInvitation, FactionMember, etc. Think about what you'll store in the database.
    The next step is to define the database structure. This is particularly important with SQL-family databases, as how you store the data may affect how you load the data into memory, leading to other optimization. For instance, using asynchronous queries will lead to an entirely different style of OOP (may use more functional programming).
    The next step is to provide a bridge between the data structure and the user interface. Wait, we haven't written the user interface yet! Exactly. Actually, with a good project proposal, you already know what you will be making in your user interface. Basically, this step is to make functions that assist your user interface. For example, the bridge for /f invite would be Faction->invitePlayer(Player), and the bridge for land protection would be Faction->ownsPosition(Position) (it should cache the value).

    And finally comes the user interface. This includes commands and event handlers, basically anything that changes gameplay. Rely on the bridge and avoid implementing the mechanism in the command executor directly in order to reuse more code. For example, in a world edit plugin, //pos1 and the wand do the same thing, and they should end up calling the same code. Otherwise, you will have to update code multiple times every time you need to change it.

    This is actually the well-known MVC approach. Your plugin will be a dish of spaghetti stirred together without separating the View (command parsing, event handling and other user interface stuff), the Model (database and data structures) and the Controller (the bridge between View and Model). In a simple plugin it is just a limited amount of cod, but in a large plugin the mess would be intolerable.
     
    Last edited: Aug 13, 2017
  2. XenialDan

    XenialDan Baby Zombie

    Messages:
    138
    GitHub:
    thebigsmilexd
    A well written guide, i thank you for creating this.
    Maybe it was mentioned by
    and you already tried to state out API parts, i want to mention them anyways. API's are extremely useful and remind of the same use like virions. API's are librarys inside plugins that can be used by different plugins, example economy or faction api's like Economy, PocketMoney and FactionsPlus.
    That way you can save creating code twice and save space.

    Another thing i want to mention is that you might want to share code snippets you created, regardless how bad or "useless" they are. Sharing is caring, it might help other people coding their plugins. If you discovered a trick to do something that you think is shareable and might be relevant feel free to upload and show it to other.

    Just don't be the guy that tries to force others to use it, i did that mistake too often.

    Have a nice readthrough and day.
     
    Jack Noordhuis and jasonwynn10 like this.
  3. SOFe

    SOFe Administrator Staff Member PMMP Team Poggit Admin Noobiest member in the PMMP Team

    Messages:
    1,760
    GitHub:
    sof3
    The main purpose of APIs is to ensure compatibility such that dependent plugins still work even if the background code is changed.
    Sharing code snippets is usually just too specific, and you can figure out it yourself before even finding it in the "code snippet list". Otherwise, you may want to write a commons library... which is quite pointless (look at how many people abuse jQuery, calling the library functions even if they only expect to work on one element).
    In addition, if you follow the structure I mentioned above, the "bridge" part is already the API and you don't need extra work on it. The only exception is the events API.
    Nevertheless, you need good skill in OOP to create a truly flexible event API (see what I mentioned about configurable values in the OP). See HereAuth's Registration Creation Event as an example -- it provides true flexibility on the code flow, not simply "change some variables or stop executing".

    There is a simple rule of thumb: If you noticed a big batch of messy code, try converting them into interface implementations. A few examples that I immediately think of:
    • In HereAuth, the registration code looks messy because the player has to be prompted several times and I have to store which step the player is in and other temporary data from player input. Separating each step into a RegistrationStep subclass improved code organization and even allows adding/changing steps.
    • In Poggit's plugin submission form page, all the HTML code and default value auto-filling made the source code really confusing. Eventually I formatted each input field into an exportable object dynamically at server-side code (Model) and present them with jQuery at client-side (View + Controlling).
    Even if you aren't interested in adding an external API, OOP also helps with maintaining your plugin in the future (adding features, fixing bugs, etc.) as the readability is increased.
     
    Last edited: Aug 29, 2017
  4. RousselCrft

    RousselCrft Spider

    Messages:
    10
    I have a idea.
    A plugin of races.
    With 2 o more races and yours world and resources for every race!
    Thank you so much for this tuto :)
     
  5. SOFe

    SOFe Administrator Staff Member PMMP Team Poggit Admin Noobiest member in the PMMP Team

    Messages:
    1,760
    GitHub:
    sof3
    How is it related to this thread?
     
  6. SOFe

    SOFe Administrator Staff Member PMMP Team Poggit Admin Noobiest member in the PMMP Team

    Messages:
    1,760
    GitHub:
    sof3
    By the way, as you can see in the organization of the OP, almost half of it is about writing a good plugin proposal. Indeed, writing a good large plugin needs a lot of planning. Without planning, you will end up with a lot of unmaintainable code because the logic would be too scattered, and you keep missing a lot of assumptions that you may want to change in the future (which can easily cause many bugs).
     
  7. SOFe

    SOFe Administrator Staff Member PMMP Team Poggit Admin Noobiest member in the PMMP Team

    Messages:
    1,760
    GitHub:
    sof3
    I forgot to elaborate about plugin cooperation. For example, I have a warp plugin to teleport to another world, and I have a pet plugin. Would it be nice to have an option to leave the pet in the old world or bring it along, instead of asking the player to "sit" it every time?
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.