Three weeks ago I started working on my game and got really far. Development was a breeze. But I knew I had some architectural problems that I wanted to fix so I could reuse portions of the code in my next game. I haven't done anything in two weeks except stare at the same errors.
I'm trying to write my game in a very procedural style. I am specifically avoiding using any kind of OOP (and not interested in discussing its merits here).
The problem I've run into is that some modules in my code are highly mutually dependent and this leads to circular requires in lua.
e.g.
My event dispatcher will receive an event through a raise_event() call. It will then loop over its listeners and call an event handler function depending on what kind of listener it is (e.g. player_handle_event() or monster_handle_event()). Within these handlers, new events may be raised.
The problem is that in trying to separate my code into clean modules and keeping all the event handlers and event dispatcher in separate files... I can't figure out how to include them in the other without creating a circularity.
The dispatcher needs to know about the event handlers and the event handlers need to know about the dispatcher and I would prefer not jamming everything into one file.
paralyzed by engine architecture problems
Re: paralyzed by engine architecture problems
Why do the event handlers need to know about the dispatcher?
Could you show a minimal example of your event system?
Could you show a minimal example of your event system?
Re: paralyzed by engine architecture problems
While this might not be the solution you're looking for, do you know that it's possible to reference variables in functions that don't exist when the function is created? So you could keep things as global variables, and have as many references back and forth as you like, and require them in any order you like, as long as you start calling functions after all the scripts have been required. Does that make any sense?
I would personally try to avoid this style, as it's a bit hard to maintain, but it could be worth it if it solves some bigger problem for you.
I would personally try to avoid this style, as it's a bit hard to maintain, but it could be worth it if it solves some bigger problem for you.
My game called Hat Cat and the Obvious Crimes Against the Fundamental Laws of Physics is out now!
Re: paralyzed by engine architecture problems
Other ways might be passing the dependencies as arguments to a function of the module loaded first or having a module just for getting dependencies (loader = require "loader"; then use loader.getmodule1() or something). the latter is total overkill IMO.
I think there is no real one-fits-all solution here, if you show us your code I am sure we can come up with something that works well with it. In general its not as much an architectural issue as it seems, remember things only need to be ready when they are used and not when the module is loaded (like T-bone said).
I think there is no real one-fits-all solution here, if you show us your code I am sure we can come up with something that works well with it. In general its not as much an architectural issue as it seems, remember things only need to be ready when they are used and not when the module is loaded (like T-bone said).
Re: paralyzed by engine architecture problems
Because events can be raised within the event handlers.undef wrote:Why do the event handlers need to know about the dispatcher?
Could you show a minimal example of your event system?
Something like this (not exact but close enough to the idea)
Code: Select all
dispaptcher.lua
local event_handlers = require('event_handlers')
local dispatcher = {}
function dispatcher.dispatch_event(dispatcher, event, world)
... find listeners and call the appropriate event handler ...
(in this case it will be mimic_event_handler())
end
function dispatcher.new_event(name, value)
local e = {}
e.name = name
e.value = value
return e
end
return dispatcher
Code: Select all
event_handlers.lua
local dispatcher = require('dispatcher')
local event_handlers = {}
function event_handlers.mimic_event_handler(object, event, world)
if event.name == "PLAYER_SHOOT" then
dispatcher.dispatch_event(world.dispatcher, dispatcher.new_event("MIMIC_SHOOT", object), world)
end
end
return event_handlers
Last edited by r-hughes on Thu Mar 10, 2016 2:54 pm, edited 6 times in total.
Re: paralyzed by engine architecture problems
in your example neither file needs to require the other, because all functions live in the global environment and therefore do not need to exist at the time of being referenced in another function.
Re: paralyzed by engine architecture problems
A system that makes adequate use to events to manage global state (and is very popular right now amongst web developers) is called Flux. But the reason it gets away with what you're trying to accomplish is mostly because of the declarative nature of the render tree (AKA React). Events are typically only created from within the context of those largely immutable views. And then those views are optimized to consume data modified by events and never consume the events themselves. Also, typically, this architecture does not run at 60 FPS. The constraints on the way events flow unidirectionally and the declarative nature of the render tree make it slow relative to more imperative methods that more closely match the way that OpenGL works.
It's a bad idea to allow your event handlers to dispatch events. You're running into problems because this is a poor architectural decision (albeit a really common one). The issues with the circular dependency that you're running into now are nothing compared to what you will likely run into later when dispatching events at 60 frames per second. It will likely be the worst debugging experience you've ever had.
I want to leave you with a link to post written by John Carmack and shared by Johnathan Blow, two game developers for whom I have a lot of respect. It's about side-effect free functions and where Carmack thinks the payoffs for easy debugging and testability outweigh the performance gains of mutating global state. I hope you find it interesting. http://number-none.com/blow/john_carmac ... _code.html
It's a bad idea to allow your event handlers to dispatch events. You're running into problems because this is a poor architectural decision (albeit a really common one). The issues with the circular dependency that you're running into now are nothing compared to what you will likely run into later when dispatching events at 60 frames per second. It will likely be the worst debugging experience you've ever had.
I want to leave you with a link to post written by John Carmack and shared by Johnathan Blow, two game developers for whom I have a lot of respect. It's about side-effect free functions and where Carmack thinks the payoffs for easy debugging and testability outweigh the performance gains of mutating global state. I hope you find it interesting. http://number-none.com/blow/john_carmac ... _code.html
Re: paralyzed by engine architecture problems
Sorry. I was unclear. None of the functions exist in the global environment because they are all in packages. I have edited my post. I was just trying to illustrate the concept.S0lll0s wrote:in your example neither file needs to require the other, because all functions live in the global environment and therefore do not need to exist at the time of being referenced in another function.
So then what is the solution? How do I avoid this design?Tanner wrote:A system that makes adequate use to events to manage global state (and is very popular right now amongst web developers) is called Flux. But the reason it gets away with what you're trying to accomplish is mostly because of the declarative nature of the render tree (AKA React). Events are typically only created from within the context of those largely immutable views. And then those views are optimized to consume data modified by events and never consume the events themselves. Also, typically, this architecture does not run at 60 FPS. The constraints on the way events flow unidirectionally and the declarative nature of the render tree make it slow relative to more imperative methods that more closely match the way that OpenGL works.
It's a bad idea to allow your event handlers to dispatch events. You're running into problems because this is a poor architectural decision (albeit a really common one). The issues with the circular dependency that you're running into now are nothing compared to what you will likely run into later when dispatching events at 60 frames per second. It will likely be the worst debugging experience you've ever had.
I want to leave you with a link to post written by John Carmack and shared by Johnathan Blow, two game developers for whom I have a lot of respect. It's about side-effect free functions and where Carmack thinks the payoffs for easy debugging and testability outweigh the performance gains of mutating global state. I hope you find it interesting. http://number-none.com/blow/john_carmac ... _code.html
I suppose in the case I presented I could set a flag in the mimic or change its state in the event_handler, and then on the next update call it would act appropriately and fire off an event. To be clear, I am not modifying global state in any way.
Last edited by r-hughes on Wed Mar 09, 2016 6:08 pm, edited 1 time in total.
Re: paralyzed by engine architecture problems
Think about which part of your program should be allowed to dispatch events and then expose it to an appropriate environment.
If you want to be able to dispatch events from anywhere that would be the global environment.
I find your code a little puzzling to be honest, but I think if you would do something like that you would get the results you want:
This is a direct and simple way to approach it, and shouldn't be problematic for smaller projects.
If you're working on something bigger, you can think about placing state specific event handlers in tables and change the reference to event_handlers when you change the state.
Your dispatcher should always work the same and is therefore state independent.
One more thing you might not know about:
In Lua it is common to represent tuples as a key value pair in a table.
So your newEvent function could be implemented like this:
If you want to be able to dispatch events from anywhere that would be the global environment.
I find your code a little puzzling to be honest, but I think if you would do something like that you would get the results you want:
Code: Select all
-- main.lua or whatever your desired "upper hierarchy file" is
dispatcher = require"dispatcher
event_handlers = require"event_handlers"
If you're working on something bigger, you can think about placing state specific event handlers in tables and change the reference to event_handlers when you change the state.
Your dispatcher should always work the same and is therefore state independent.
One more thing you might not know about:
In Lua it is common to represent tuples as a key value pair in a table.
So your newEvent function could be implemented like this:
Code: Select all
function dispatcher.new_event(name, value)
return { name = value } -- don't get what value (or object) is and what you need it for but ok
end
Re: paralyzed by engine architecture problems
What part puzzles you? In my example, when the player shoots I want to raise an event for it. This is picked up by things like the audio system and decouples the player logic from the audio. I also want, in this case, to signal all mimic enemies to shoot at the player.undef wrote:Think about which part of your program should be allowed to dispatch events and then expose it to an appropriate environment.
If you want to be able to dispatch events from anywhere that would be the global environment.
I find your code a little puzzling to be honest, but I think if you would do something like that you would get the results you want:This is a direct and simple way to approach it, and shouldn't be problematic for smaller projects.Code: Select all
-- main.lua or whatever your desired "upper hierarchy file" is dispatcher = require"dispatcher event_handlers = require"event_handlers"
If you're working on something bigger, you can think about placing state specific event handlers in tables and change the reference to event_handlers when you change the state.
Your dispatcher should always work the same and is therefore state independent.
One more thing you might not know about:
In Lua it is common to represent tuples as a key value pair in a table.
So your newEvent function could be implemented like this:
Code: Select all
function dispatcher.new_event(name, value) return { name = value } -- don't get what value (or object) is and what you need it for but ok end
The object value in the event handler is necessary because the event handler IS NOT CONTAINED within the object itself. It is just a function that takes an object. The name of the event is just its name. The value of the event is a table with any additional info that might be necessary for the event (player location, which object spawned the event, et cetera). Sometimes this is an empty table if no value is needed.
I don't understand how your proposed code would work since dispatcher.lua requires event_handlers.lua requires dispatcher.lua... etc. It's still circular.
The issue is how to dispatch events from within the event handlers. Or if I even should be doing that.
Who is online
Users browsing this forum: No registered users and 5 guests