Quillraven / Fleks

Fast, lightweight, multi-platform entity component system in Kotlin
MIT License
180 stars 20 forks source link

Proposal: call system's onDispose in reverse order. #129

Closed metaphore closed 11 months ago

metaphore commented 11 months ago

Strict order is important for both initialization and disposal of the systems and it works nice right now. But I think in many cases it would be better to call onDispose in reverse order to keep any possible inter-system relations and resource management in sync within the world.

Honestly, I think it's harder to come up with an example when a forward onDispose order is preferred over the reversed. It's usually rather you don't care about onDispose order at all or need it to be in reverse. Here's a quick example of a system that uses a resource that is bound to the lifecycle of another system:

class ResourceSystem : IntervalSystem() {

    lateinit var resources: HeavyResource

    init {
        resource = loadHeavyResource()
    }

    override fun onDispose() {
        resource.dispose
    }
}

class DependentSystem : IntervalSystem() {

    init {
        val resource = world.system<ResourceSystem>().resource
        // Need to do something with the resource.
    }

    override fun onDispose() {
        val resource = world.system<ResourceSystem>().resource //!! An exception here, as the resource has been disposed at this point.
        // Need to do something with the resource.
    }
}

val world = configureWorld {
    systems {
        add(ResourceSystem()) // Higher order system
        add(DependentSystem()) // Lower order system
    }
}

world.dispose()

An argument against this example could be that the systems are all created and destroyed at the same time, and thus their lifecycle is exact. So why keep shared resources within a system, and not in a global context/injection? For me, it's for the sake of encapsulation. I find it often convenient to store and manage some shared objects within specific systems (e.g. post-processing shaders and frame buffers, system-specific renderers, etc). While global system order makes it very easy to keep track of which resource is initialized/disposed after which.

Going forward with this, when #123 is merged, the same principle of reverse destruction should be applied to the IteratingSystem's onRemove family hooks to keep it consistent with the system disposal order.

It takes only a few lines of change to implement, most likely won't break any existing projects, and opens up opportunities for proper object management within systems. Optionally bound to either a system lifetime or an entity lifetime through system family hooks.

I can prepare PR for this.

Quillraven commented 11 months ago

I think you know my point of view about this topic already ;) I suggest to not link systems together as it is against the nature and benefits of ECS. However, if for whatever reason you need to do it like that and the only change is to call remove listeners/hooks in reverse order I don't mind the change. For me personally it doesn't matter because the order is not important due to modular and independent development of systems.

Feel free to create the PR for it - thanks!

Quillraven commented 11 months ago

@metaphore : I just noticed that the global hook is called before the system's in the remove case. Didn't you want the global hook to be at the end? Or is it okay the way it is?

image

metaphore commented 11 months ago

Oh, you're right. That should follow the reverse principle as well. Could you please update it?