Difference between revisions of "Manual:Best Practices"
(Added a blurb on generic mapper) |
m (→Best Practices) |
||
Line 3: | Line 3: | ||
= Best Practices = | = Best Practices = | ||
− | : The hope is that this page will provide a place to collect a list of Mudlet 'best practices' in an effort to help people keep their Mudlet scripts and packages clean, efficient, and running smoothly. Another way to think of it is a collection of tips and tricks for making Mudlet run its best. They largely fall under | + | : The hope is that this page will provide a place to collect a list of Mudlet 'best practices' in an effort to help people keep their Mudlet scripts and packages clean, efficient, and running smoothly. Another way to think of it is a collection of tips and tricks for making Mudlet run its best. They largely fall under these categories. |
* [[#Lua|Lua best practices]] | * [[#Lua|Lua best practices]] |
Revision as of 16:18, 12 August 2021
Best Practices
- The hope is that this page will provide a place to collect a list of Mudlet 'best practices' in an effort to help people keep their Mudlet scripts and packages clean, efficient, and running smoothly. Another way to think of it is a collection of tips and tricks for making Mudlet run its best. They largely fall under these categories.
- Lua best practices
- Since Lua is the scripting language Mudlet makes use of, the majority of the best practices for Lua will apply when writing Mudlet scripts.
- We do not aim to replace the internet as a source of Lua knowledge, but will try to highlight the most beneficial ones
- Potential exceptions will be noted with their entries
- GUI best practices
- keeping your UI looking it best in as many situations as possible
- Mudlet best practices
- Optimizations and items specific to Mudlet's triggers, aliases, API, etc
- Generic mapper best practices
Lua
Use local variables
You should really be using local variables wherever you can. If a variable is only needed for the duration of a function, alias, or trigger, then make it a local every time. If you will need to access it repeatedly in your script, make it local first. Always be asking yourself, "Could I be using a local instead?"
-- bad
function myEcho(msg)
transformed_msg = "<cyan>(<yellow>Highlighter<cyan>)<reset>" .. msg
cecho(transformed_msg)
end
-- better
function myEcho(msg)
local transformed_msg = "<cyan>(<yellow>Highlighter<cyan>)<reset>" .. msg
cecho(transformed_msg)
end
There are three main reasons for doing so
- Making things local keeps them from colliding with variables in use by other package developers. You are probably not the first person to use "tbl" for a temporary table name.
- You can make the code easier to both read and write.
- "self.mc[tabName]" is a bit much to type and remember, but if you first do "local console = self.mc[tabName]" then it's obvious you're referring to a console where you use it in future
- It's faster
- local variables are inherently faster in Lua, as they exist in the virtual machine registers and are a simple index lookup to access, whereas globals reside in a table and are therefore a hash lookup
- this means you can increase the speed of intensive processes just by making functions and variables local before using them.
- For instance, if you are going to cycle through a large table and cecho items from within it, adding "local cecho = cecho" above the loop can improve performance.
- local variables are inherently faster in Lua, as they exist in the virtual machine registers and are a simple index lookup to access, whereas globals reside in a table and are therefore a hash lookup
Avoid overwriting your variables accidentally
Often times you need to initialize your tables at the top of your scripts. But if you click on that script again later and back off it will save and overwrite your current data. You can use 'or' to avoid this as shown below.
-- have to make a table before you can add things to it
-- risks overwriting the entire table if I resave the script
demonnic = {}
-- if the variable has already been defined, it is just set back to itself. If it has not, then it's set to a new table
demonnic = demonnic or {}
Ternary statements in Lua (set a variable based on a condition)
Lua does not have a ternary operator as such, but you can construct a statement in such a way it functions as one, allowing for shorter and more readable code. For example:
-- you can rewrite this if statement
if something then
myVariable = "x"
else
myVariable = "y"
end
-- as
myVariable = something and "x" or "y"
Group your globals together in a table
Sometimes you have to use globals, to pass values easily between items or make functions and values available to others. Lots of package makers may have a reason to track health and want to be able to pass it easily between triggers and functions without having to make it a parameter for every function they write. Obviously they can't all use the 'health' variable though, especially if one is using percentages and the other direct values. So it's best if you keep the variables for your package grouped together within a table. "Demonnic.health" is a lot less likely to collide than just 'health' There is a forum post with more information.
Declaring functions in tables: myTable:function() vs myTable.function()
Lua does not have as much syntactic sugar as some other languages, but using a : to call a function within a table is one of them. The following declarations are functionally identical
function myTable:coolFunction(parameter)
self.thing = parameter
end
function myTable.coolFunction(self, parameter)
self.thing = parameter
end
myTable.coolFunction = function(self, parameter)
self.thing = parameter
end
And these calls are equivalent
myTable:coolFunction("Test")
myTable.coolFunction(myTable, "Test")
You can use this to make it easier to keep your code self-contained and grouped together. You can combine it with setmetatable() to allow a sort of Object Oriented like programming pattern. Which leads us to
Create objects and 'classes' using metatables and :
Geyser makes extensive use of this pattern and it allows you to create classes and objects within Lua's procedural environment. The chapter in Programming in Lua on Object Oriented Programming does a good job of explaining it.
GUI
Use percentages
A lot of your users will be on different resolution monitors, or may not want to have Mudlet covering their entire screen. If you hardcode your sizes as direct pixel values for your Geyser objects, they will not scale as well when the window is resized due to resolution restrictions or user preference. If you're personally working on a 1080p resolution UI but you want it to scale up or down, rather than using "640px" as the width to cover a third of the screen use "33%" for the width, and it will resize to cover a third of the window no matter the size.
Adjustable Containers put the power back in your user's hands
If you use Adjustable.Container for groupings of your UI it allows your users to resize and move them around if they don't like the way you've arranged things. This means more people using your package in the end.
Mudlet
Shield your complicated regular expression triggers with a substring trigger
- Regular expressions are very useful, but are one of the slower trigger types to process for Mudlet. Using a substring trigger gate or a multiline trigger with the substring trigger ahead of the regular expression and a line delta of 0 you can reduce the processing time of your triggers overall by quite a bit. There has been benchmarking done on the forums: There has been benchmarking done
Avoid expandAlias where possible
- There's a whole wiki page on this at Functions_vs_expandAlias
Package and Module best practices
- This is a list of things we have discovered over the years leads to the best user experience with Mudlet packages. They aren't all necessary, but you will find that the more of them you do, the easier the lives of your users and ultimately yourself.
- built-in auto-updates
- so players stay up to date, which will not be the case with manual updates)
- modular and extendable - use Mudlet modules for this
- so people can disable ones not needed)
- event based - raise events of your own for others to hook into
- so the order of scripts installed in the players profile doesn't matter
- make sure aliases only call functions
- so people can make their own aliases or keybindings to customize as needed without using expandAlias
- don't pollute the global namespace, keep everything in a table
- so you don't bugs from people overwriting it or vice versa)
- touched on above but bears repeating
- undo any UI changes on uninstall: set borders back, hide the UI, etc
- so people have a good experience even if they didn't like the package
- if you're specifying any fonts, package them
- while it might be available on your computer, not guaranteed to be available on every computer
- if you're a game admin, install it automatically via GMCP
- less overhead for players to get a good experience
- if you're a game admin, provide GMCP data for your game
- so people don't have to waste time trying to capture data, and can work with it instead
- don't use \ in paths, use / - even for Windows
- it'll work on Windows - and work on macOS and Linux too
Generic Mapper
- when adding customisation triggers to the generic mapper, add them outside of the
generic_mapper
folder- this is so when the mapper updates, you keep your changes