-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New Library: AceState! A state lifecycle management library implementing the action/reducer pattern to update immutable state. Works well in tandem with AceDB. #22
Conversation
I have not tested this thoroughly yet so I have left this PR marked as a draft. I'll be implementing this in my actual addon shortly and testing it over the next few days. When I'm satisfied it's working as intended and stable then I will turn this into a non-draft PR. |
end | ||
|
||
-- Return a dispatch function for this reducer | ||
local dispatch = function(actionType, payload) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create the actual dispatch function that will invoke this reducer when called. We return this dispatch function directly from RegisterReducer
but we also store it on self.dispatchers
so our self:Dispatch
method can iterate over each reducer's dispatch function and invoke it.
if newState[k] == nil then | ||
self.state[name][k] = nil | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This bit here is logic that transplants the updated state properties onto the original state object. By transplanting properties onto the original object we ensure that any references to that original object remain intact. If we simply overwrote the original object with the new state then any existing references to the original object would be invalid.
for _, dispatch in pairs(self.dispatchers) do | ||
dispatch(actionType, payload) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main "meta" dispatch function, invokes all other stored dispatch functions.
In general, I think you would be better served with a standalone library. There is really no benefit of including it, considering you can literally just take your file and publish it as-is separately, perhaps with the library renamed. The all in one package design is from a long time ago, and today I would probably not do it like this anymore, especially when a library is entirely independent and does not depend on any of the others (which we designed most to be, but not all could do that) As for some general feedback, on a first glance I think "Dispatch" name for an embed function is a bit too generic, maybe DispatchState would be better. Also thinking about the dispatching and how your reducer function still has to carry a lot of it, it feels like a simple CBH dispatcher would cover about 90% of this already. |
res[self.DeepCopy(k, s)] = self.DeepCopy(v, s) | ||
end | ||
return res | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This deep copy logic ensures that reducers only get a cloned state object, preventing them from modifying the original object by reference. Keeping it immutable within the state machine.
Got it. I'll take my code elsewhere. Thanks for taking a look. |
As a web developer I've gotten used to so many quality of life tools and libraries. When I invariably come back to dabble in WoW addons with Lua I quickly remember how much low level work will be involved once an addon starts to become reasonably complex. One of these annoyances is keeping state manageable. Most recently I had to synchronize data between multiple clients to keep quest data updated amongst all players in the same party for my QuestTogether addon. This took an already working addon and immediately ballooned the complexity by a couple orders of magnitude.
I now had many functions trying to modify the same state. The addon has to work while solo too so I can't rely on party comm messages for local operations. For example, I'd have to update a giant
questTracker
object with the player's current quests and objectives, but now that I want each client to keep track of each player's quests in a party I had to expand the object to contain multiple quest trackers by character name and I had to create broadcasts that would send out quest updates from each client to all other clients so they too could update their copy of each player's quests.Basically I had to run similar logic in comm received functions and various WoW event handler functions. It was becoming quite unweildy to keep track of it all. I needed a proper state machine. That's when it hit me that it's time for me to build a library and implement the action/reducer pattern. But then I quickly realized that this functionality would fit perfectly into the Ace3 suite! So rather than publish my own standalone addon I figured I'd take a stab at adding it here. If for some reason this is not desired then I'll go ahead and publish a standalone library, but hopefully you feel similarly that this is a elegant and lightweight solution that adds a cool way to reduce addon state management complexity without adding unnecessary bloat to Ace3 itself
Without further ado, here is a basic usage example showing how one would use AceState: