LastOliveGames / becsy

A multithreaded Entity Component System (ECS) for TypeScript and JavaScript, inspired by ECSY and bitecs.
MIT License
202 stars 17 forks source link

Terminating the world and creating a new one with the same components results in an error #14

Closed spendo-atl closed 2 years ago

spendo-atl commented 2 years ago

Likewise when returning to routes that do rely on the becsy world we have the need to create a new world with a completely different set of entities.

We have been attempting to terminate the world, and create it again when returning - however terminating and starting a new world with the same components currently results in the following due to some global state for said components:

Uncaught (in promise) Error: Component type XComponent is already in use in another world
at Dispatcher.startFrame (dispatcher.ts:320:13)
    at FrameImpl.begin (schedule.ts:298:21)
    at Dispatcher.execute (dispatcher.ts:296:24)
    at World.execute (world.ts:97:30)

Is there a possibility of supporting this use-case: (terminating becsy, and creating the world again with the same components)?

pkaminski commented 2 years ago

Heh, as you can imagine this is not a use case I had considered! I think it would be reasonable for a world to back out all global (static) decorations when terminating, which should leave the slate clean for a new world to be created. I'm a bit busy with "real work" right now but should be able to take a look this weekend, or next at the outside. (Or happy to take a PR: you'd need to add a terminate method to Registry that undoes all the work done by assimilateComponentType and defineAndAllocateComponentType, to be invoked from Dispatcher's terminate.)

In the meantime, a workaround is to set NODE_ENV to test, since as it happens tests have pretty much the same problem and I decided to just hack it. :)

spendo-atl commented 2 years ago

Thanks for the tips on where to start and for the epic response time as always!

We'll try to get a PR your way in the next day or so - glad to hear you're happy to support the use-case

spendo-atl commented 2 years ago

Actually, do you know if it would be possible/sane to remove all the entities from the world instead?

pkaminski commented 2 years ago

That could work as well, but there'll be a performance penalty as queries will need to process the deletions to ensure internal state remains consistent. Iterating through all entities will also be a bit of a pain as you'll need to iterate over all possible entity IDs (up to maxEntities) and check the shape table to see if the entity exists. If invoked outside a frame it would probably be best to run it from within the user callback system (Dispatcher.executeFunction) to ensure that everything is cleaned up properly.

pkaminski commented 2 years ago

I went ahead an fixed World.terminate to disentangle all the component types in 0.11.0. (It's now also async, beware.)

I think clearing all entities can already be done with a system so I'm less inclined to provide dedicated support for it. You can give the system a query with no constraints, stop it from running, then resume it only when you need the world cleared. A bit clunky but it should do the job.