Mixin methods: Or "How to modify lua methods without reducing compatibility"


#1

Hi guys!

I apologize if this is known knowledge, and I am about to proudly proclaim advice everyone has been following for years…

but I have read a few places things along the lines of “If you modify a lua function in your mod, you are going to run into compatibility issues with other mods that modify that function” as well as mods having a compatibility section in their descriptions saying “Hey, I have overridden this method so I will break if used with another mod that does that”

However, you can use a technique I picked up from my RPGMaker days which they called “Aliasing”.

For example, I wanted in my Kai Monkey’s Traits mod to have traits be randomized the customization options are randomized. I could do this by overriding the method as follows:

function GameCreationService:_randomize_citizen(citizen, pop, gender, locked_options)
   locked_options = locked_options or {}
   if not locked_options.name then
      pop:set_citizen_name(citizen, gender)
   end

   -- pop:regenerate_stats(citizen, { embarking = true }) -- COMMENT THIS OUT
   local cc = citizen:get_component('stonehearth:customization')
   cc:regenerate_appearance(locked_options.customizations)
   pop:regenerate_stats(citizen, { embarking = true }) -- AND MOVE IT DOWN HERE
end

However this has all the compatibility issues discussed above. Instead I did the following

local old_randomize_citizen = GameCreationService._randomize_citizen
function GameCreationService:_randomize_citizen(citizen, pop, gender, locked_options)
    -- Do anything I want to inject before the method does it's normal thing
   old_randomize_citizen(self, citizen, pop, gender, locked_options)   -- NOTE the "self" as the first parameter
   -- Do anything I want to inject after the method does it's normal thing, in this case re-rerandomize the traits
   pop:regenerate_stats(citizen, { embarking = true })
end

This means I can proudly announce my mod knowing that if there are any compatibility issues they are not my fault. If everyone uses this method as often as they can, we will reduce this issue greatly!

This method will only ever work when you’re injecting stuff into the code base, and will not let you (easily) remove logic. However, it is worth noting that you can do fun things with modifying the parameters the old method is called with, as well as injecting logic before and after.

Of course once it gets to actually having multiple mods working together the results may become strange and unexpected, (depending on which order the lua is executec) however a lot of the time its much much better to have them work strangely than not at all.

I can write up an example of this if someone would like


#2

You could “remove” a function with your method by having the function do nothing when called. Yes, I agree, this is a fantastic way of mixing stuff in. I would think it’s only really viable for large mods, though, because in order to mixin lua files at scale, you need a script running before the main game, doing all the patching. Including such a script in every mod you release seems a nuisance.


#3

Hi Moai,

If you’re not calling the original function then you are just overwriting the method as per normal, which doesn’t increase compatibility.

Also, whenever you are defining functions anywhere you are running that code on game start. In my mod I just shoved that method at the end of one of the traits lua pages without much concern about what file it was in

EDIT: And then then came back to bite me, looks like @Moai was right, I will have to think about where to include this after all…


#4

It’s a bit of a nuisance to do a server_init_script and/or a client_init_script, but I don’t think it’s really any more of a nuisance than, for example, having to specify your Ember-accessible functions in your manifest and add call handlers for them.

But it still certainly has its limits in terms of multiple mods trying to affect the same function. You could even end up with infinite loops if people have different ideas about how to do things, with each mod working perfectly fine on its own.

Edit: Another problem is multiple different modders figuring out that the only way to get their mod to work is by doing a particular mixin, so they have to include that with their mod; they’re adding different content, but have to do the same Lua hack to get it to work, and even if they do exactly the same thing it could cause both of their mods to fail. Imagine modifying a function to trigger an event when it’s done; if the other modder did the same thing, naming their event the same and choosing the same object to trigger it on, both mods will experience double events being triggered. So then it becomes important to try to isolate Lua mixins from content of any type and make those mixins dependencies. Early World of Warcraft UI (Lua) modders did this, though I’ve been (literally) out of the game so long I forget what those libraries were called.


#5

If try alternative is the mods being garenteed to be incompatible though, it’s strictly and improvement, even though it’s far from flawless


#6

It’s called Monkey Patching. And it’s art. I’ve probably written up something about it quite a few times over the past five years. Keep in mind that in your example, you’re not returning any values whatsoever - which might break stuff if the callee is expecting them.

It’s a means to an end, but nothing that I would call “reducing compatibility issues”. The problem is that it only works as long as mods are nicely adding functionality on top of each other. As soon as one mod decides to change the control or data flow, you’re either going to get nasty side effects or straight up errors. This gets even funnier if you have no control over the order in which they are patched.

The goal should be - and that’s what I kinda tried with Jelly a long, long time ago - to have one single mod to do the monkey patching and provide proper functionality in its place, basically acting as a negotiator/middleman. That way, functionality can be assured for all mods, and it’s even possible to remove or prevent functionality (e.g. by providing a table with a bool - any mod sets the bool to false, that functionality isn’t used. Get creative, lots of ways to do such things).

Especially now that the game’s end-of-life is basically around the corner, it’s feasible for a mod to just straight up override whole functions/files without worries about the developers adding/changing something that would break the functionality. So get that one mod act as a library for all the other mods and call it a day - and then, no (or very little) monkey patching would be required.


#7

After reading about the semantics of Monkey Patching what I’m suggesting is apparently advocating the “alias chain method” (that is, calling/returning the old version of the method as part of your monkey patch) instead of standard monkey patching without that original reference.

I believe my mistake was to assume that that was already the norm.

I guess the mentality that the RPGMaker community had many years ago when I was a part of it was that if you’ve got two mods that need to patch the same base method, if you alias_chain it there’s a chance that things will work out, while if you instead override the method then we know one will lose and result in a broken mod.

Of course it goes without saying that the best way to avoid comparability issues it to not need to make changes to the base code :wink:


#8

Even if you call it aliasing or chaining, what you’re effectively doing is overwriting the method. It’s more specific in what you do, but viewed from an abstract point of view, you’re replacing one function with another.

Not to mention that each patch has performance and memory overheads to deal with. But I believe RPG Maker uses Ruby (or at least, XP did?), so I guess performance wasn’t high on the list of worries to begin with.