Circular Require Woes
Forum rules
Before you make a thread asking for help, read this.
Before you make a thread asking for help, read this.
Circular Require Woes
Hi, again.
It seems many moons ago I started a thread about problems I was having with circular requires. Well, they've struck again and I still don't have a good solution that doesn't require strange contortions of my architecture.
Let me explain the situation I keep running into.
I am trying to keep the code for my game in separate modules. For example, the code for creating, updating, and destroying players is in 'player.lua.'
I also have a module for handling creating, updating, and destroying scenes, 'scenes.lua.'
In 'scenes.lua,' I must require 'player.lua,' because many scenes will need player objects in them. However, in 'player.lua,' I would like to be able to call the function scenes.destroy() on the current scene, when the player dies. This means I have to require 'scenes.lua' inside player.
Obviously, this is a circular dependency.
There are a few ways to avoid this, but I'm not a big fan of any of them.
1) Use globals.
2) Stuff functions in the tables and use them like OOP classes.
3) Separate everything into separate modules 'create_player.lua', 'destroy_player.lua', etc...
4) require() inside the functions
Previous explorations of this issue have used resulted in advice to either do one of those three things and, vaugely, to 'rethink' my architecture.
It'd be super helpful if you could give me any insight as to how to solve this problem. Thank you.
It seems many moons ago I started a thread about problems I was having with circular requires. Well, they've struck again and I still don't have a good solution that doesn't require strange contortions of my architecture.
Let me explain the situation I keep running into.
I am trying to keep the code for my game in separate modules. For example, the code for creating, updating, and destroying players is in 'player.lua.'
I also have a module for handling creating, updating, and destroying scenes, 'scenes.lua.'
In 'scenes.lua,' I must require 'player.lua,' because many scenes will need player objects in them. However, in 'player.lua,' I would like to be able to call the function scenes.destroy() on the current scene, when the player dies. This means I have to require 'scenes.lua' inside player.
Obviously, this is a circular dependency.
There are a few ways to avoid this, but I'm not a big fan of any of them.
1) Use globals.
2) Stuff functions in the tables and use them like OOP classes.
3) Separate everything into separate modules 'create_player.lua', 'destroy_player.lua', etc...
4) require() inside the functions
Previous explorations of this issue have used resulted in advice to either do one of those three things and, vaugely, to 'rethink' my architecture.
It'd be super helpful if you could give me any insight as to how to solve this problem. Thank you.
- DanielPower
- Citizen
- Posts: 50
- Joined: Wed Apr 29, 2015 5:28 pm
Re: Circular Require Woes
One option would be to pass 'scene' to your player. In your scene.lua file:
Then you should simply be able to access player.scene, or self.scene from your player.lua file.
Code: Select all
local player = require('player')
player.scene = scene
Re: Circular Require Woes
That's definitely an option, but it seems a bit weird to pass in an entire module like that... I usually reserve table values for actual properties of that table, as though it were a struct, except in the case of tables acting as modules... in which case they simply store functions.
- DanielPower
- Citizen
- Posts: 50
- Joined: Wed Apr 29, 2015 5:28 pm
Re: Circular Require Woes
If you don't want to use globals, you need to give 'player' a local variable that lets it find the scene that created it.
Also, in terms of performance, you're not really passing an entire module. Simply creating a link. 'player.scene' will be the exact same variable as 'scene' in scene's scope. So any modifications to 'scene' will be reflected in 'player.scene'.
Something I often do for these cases is prefix the variable name with an underscore to signify that it is a link to another library, and not part of this library.
player._scene = scene
Alternatively, you could have a parent module create and maintain a list of your modules. You could then access scene from player with 'self.parent.module['scene']. Although this is an uglier solution in my opinion.
Also, in terms of performance, you're not really passing an entire module. Simply creating a link. 'player.scene' will be the exact same variable as 'scene' in scene's scope. So any modifications to 'scene' will be reflected in 'player.scene'.
Something I often do for these cases is prefix the variable name with an underscore to signify that it is a link to another library, and not part of this library.
player._scene = scene
Alternatively, you could have a parent module create and maintain a list of your modules. You could then access scene from player with 'self.parent.module['scene']. Although this is an uglier solution in my opinion.
Re: Circular Require Woes
I understand that it's just a reference, but it seems so *weird* to me.
Maybe this is a case where globals are the answer, despite everything telling me 'no!' Keeping all modules in the global scope might be alright.
Maybe this is a case where globals are the answer, despite everything telling me 'no!' Keeping all modules in the global scope might be alright.
Re: Circular Require Woes
This is what I've done when I needed a quick and dirty solution to the problem
So you can load the modules without any dependencies at all, and as soon as you've loaded the module, require() clocks out: it's done its job, and it can just give you the already loaded module next time you need it.
I do think it's worth thinking of a way to not have mutually recursive modules in the long run, though.
Code: Select all
--- module 'this'
function this.new()
local that = require 'that'
that.thing()
end
--- module 'that'
function that.new()
local this = require 'this'
self.add(this.new())
end
--- module 'main'
local this, that = require 'this', require 'that'
that.new()
I do think it's worth thinking of a way to not have mutually recursive modules in the long run, though.
Re: Circular Require Woes
This has been repeated as mantra, but never explained. How would I do that in this situation? I appreciate your post, but I've failed to understand what the solution is in terms of architectural design and nobody has explained it beyond saying 'you're doing it wrong.'I do think it's worth thinking of a way to not have mutually recursive modules in the long run, though.
I don't think my design is completely unreasonable. I need to dispose of the scene when the player dies. The most straightforward way to do that is to call destroy_scene() in destroy_player(). Evidently, this is wrong. So let's think about ways to avoid the circular require completely that don't involve contortions of the codebase or the use of many globals to hold modules of functions.
Re: Circular Require Woes
It sounds like you're using the function destroy_scene() to access some state, presumably doing something like
My question is, why not you make that dependency explicit, either using an explicit scene object or passing in the whole module (which is semantically the same as an object) as your function argument?
Code: Select all
local my_scene = { ... }
function scene.destroy()
do_thing(my_scene)
end
Code: Select all
function player.destroy(child)
child.destroy()
end
- Positive07
- Party member
- Posts: 1014
- Joined: Sun Aug 12, 2012 4:34 pm
- Location: Argentina
Re: Circular Require Woes
Callbacks?
In scene.lua
in player.lua
Another option is a function like this
in player.lua
But heck this is all ugly because your player is trying to do stuff it shouldn't!! You should have a game.lua module which handles your game logic, when the player performs some actions your game logic would destroy the scene, not your player!
The idea basically is that all your modules do one thing and one thing only, your player.lua module should only take care of your player, no collision detection, NPC interaction or moster fighting should be done here.
Same with monsters and everyting else
Your game logic is in a separate module which handles some of this stuff (you could split everything even more)
When a module depends on a parent module you are splitting stuff in a wrong way, probably that child is doing too much and should let the parent handle those situations, maybe even a higher parent. In this case maybe scene can take care of what the player was trying to do, if it's not his job either maybe a parent module should do it, as I proposed some kind of game.lua module.
In scene.lua
Code: Select all
player.destroyScene = function ()
scene.destroy()
end
Code: Select all
function player:doSomething ()
self.destroyScene() --Ugly but works
end
in player.lua
Code: Select all
local scenedestroyed = false
function player:doSomething()
scenedestroyed = true
end
function player.hasDestroyedScene()
return scenedestroyed
end
The idea basically is that all your modules do one thing and one thing only, your player.lua module should only take care of your player, no collision detection, NPC interaction or moster fighting should be done here.
Same with monsters and everyting else
Your game logic is in a separate module which handles some of this stuff (you could split everything even more)
When a module depends on a parent module you are splitting stuff in a wrong way, probably that child is doing too much and should let the parent handle those situations, maybe even a higher parent. In this case maybe scene can take care of what the player was trying to do, if it's not his job either maybe a parent module should do it, as I proposed some kind of game.lua module.
for i, person in ipairs(everybody) do
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
[tab]if not person.obey then person:setObey(true) end
end
love.system.openURL(github.com/pablomayobre)
Re: Circular Require Woes
Boy, how it is easy to forget that "you should change your architecture" means next to nothing to the one who doesn't know "what?", "how?" or even "why do I have to?" to change it.
@r-hughes
My suggestion would be setting a simple field of a player to true. Like this
Monitor that field in your scene module and destroy current scene in the scene module if that field was set to true.
You may also set that field to false immediately, so that your new scene won't be destroyed on the next update.
That's the simplest way in your case.
@r-hughes
My suggestion would be setting a simple field of a player to true. Like this
Code: Select all
player.scene_should_be_destroyed = true
You may also set that field to false immediately, so that your new scene won't be destroyed on the next update.
That's the simplest way in your case.
Who is online
Users browsing this forum: Bing [Bot], slime and 6 guests