Spell of Mastery Progress Still rewriting the engine.
Earlier I've converted everything to use ECS.
That opened a venue to fixing the general architecture.
At the moment I'm decoupling video and audio parts from the entities.
With ECS both are just the systems sampling entities and producing output.
Before ECS they were highly coupled with the entity actions.
For example, animation code had to tell when the attack animation hits.
That led to horrible bugs and general inflexibility.
I.e. it was impossible to break any actions.
And animation code had to be run even for the off screen entities.
Because otherwise these entities couldn't act.
Beside uniformity, as an added bonus, the code became faster.
Because only the entities really needing that get processed.
Moreover, systems inside the same phase could run on different CPUs.
As long as they dont write into each other systems.
Compared to OOP, ECS guides you towards robust architectural choices.
Of course my success with ECS is due to first re-introducing the complete OOP.
That way I could have gradually transitioned my messy code to proper ECS.
Untangling it component by component.
I haven't implemented method phase yet, so it involves manually calling the systems:
update_systems Me =
update_custom_systems Me
for U each(add_cover){unit_}: add_cover U
for U each(add_shadow){unit_}: add_shadow U
for U each(remove_sat){unit_}: remove_sat U
for U each(place_sat){unit_}: place_sat U
for U each(suffers){unit_}: upd_suffers U
for U each(motilize){unit_}: motilize U
for U each(active){unit_}: U.do_activation
for U each(needs_globals){unit_}: U.add_global_acts
for U each(anm1){unit_}: U.update_anim
for U each(made_step){unit_}: made_step U
for U each(anm_float){unit_}: U.reset_visl
for U each(animate){unit_}: U.do_animate
for U each(randidle){unit_}: U.rand_anim_step
for U each(goal){unit_}: U.break_idle
for U each(ordered){unit_}: U.break_idle
for U each(members_){unit_}: update_members U
for U each(fade_inc){unit_}: U.update_fade
for U each(acting){unit_}: update_action U
for U each(pursued){unit_}: update_pursued U
for U each(actpost){unit_}: update_actpost U
for U each(enchng){unit_}: update_enchng U
for U each(fall){unit_}: update_fall U
for U each(sqid){unit_}: update_sqid U
for U each(host){unit_}: update_hosted U
Ugly but already much cleaner than when all this nonsense was buried and coupled in a spaghetti mess.
Note that each system there converts the table to the `unit` interface. These are similar to Java or C# interfaces, in that they don't have data, only methods, allowing me to work with an entity like with an OOP object, abstracting away the tables behind it. So I don't really lose any OOP benefits, and due to Symta being bytecode language, hashtable performance loss is negligible.
Further more, by allocating entity ids inside specific ranges it is possible to have subtypes of entities with non-hash table. For example, if ones decides to implement terrain cells as entities, one can reserve an id range for them, so pathfinder lookup will be fast enough. In fact, I did that for my terrain cells even before ECS, because a few cells needed to have expensive metadata. So using a monolithic struct for all cells was just not an option. Finally I have a solid framework instead of disjoint mess of tables.
I think some people use ECS outside of video games or graphics. I myself use it for widgets in my voxel editor. And you can easily replace ReactJS with ECS. In general you can use ECS everywhere you have a SQL database or need a Prolog style processing. In fact, some components and systems could remain inside the database, or even across multiple servers. But the system `phases` will be different for each use case.
For example, you can represent each row in a text file as an entity, and every character inside of it as another entity. Then each row number could be made into a separate component, and same with column number. Now you can implement programs like grep as systems over entities, and even run them in parallel in the same phase on different rows. And different phases can be seen as separate programs connected through pipes. The components can also be seen as messages, and system can consume them by clearing the table after processing everything.
Console.println = "hello world"
sets component println, which could be printed asynchronously. The paradigm is kinda profound.
The weak point of ECS is that everything is inherently async. So if you want to do have everything in sync, it will be really slow, because ECS have to go through all the phases, before performing the next step. That is okay for rendering a web page, but not okay for say running a CPU emulator.
Current Mood: amused