GodotECS/godex

Scene tree interaction using Godex systems and databags for commands.

Closed this issue · 3 comments

I want to introduce one idea that can help reduce the coupling between Godex ECS and Godot. It gets possibilities of managing Godot scene tree in ECS manner and potentially multithreaded. It helps to stay in pure systems architecture without direct mixing several architecture approaches, and reduce code complexity.

All feature will be consist of SceneTreeCommandDatabag that stores all further commands, and scene_tree_manager_system().

Features

The minimal set features is:

  • Spawn scene instance from ".tscn" file
  • Spawn specific node by Class (Maybe dont needed either)
  • Move node to another parent
  • Remove Node
  • Rename Node
  • Get node by scene tree path (NodePath)
  • Find node by name
  • Find Node by type

I also want to suggest thinking about the order in which to perform all commands, because it can this may affect the final result or possible errors. Ordering in the list above should works.

Every feature above will be represented as LocalVector<_Some_Command> _cmd and the corresponding part of the code in the system that should execute that command in some time.

Example:

How databag should look like:

struct AddSceneFromFileCmd 
{
	 // Maybe for res:// path exist special class? 
	 String res_path;      // Example: "res://scenes/test.tscn"
	 NodePath parent;  // Node where scene should be spawn.
}

class SceneTreeCommandDatabag : public godex::Databag {
	DATABAG(SceneTreeCommandDatabag)

        LocalVector<AddSceneFromFileCmd> add_scene_from_file_cmds
        // Other Command arrays

public:
	SceneTreeCommandDatabag();

	// Too verbose naming :P
	 void add_scene_from_file(String res_path, NodePath parent);
	 
	 // return cloned command and deletes it from LocalVector 
	 LocalVector<AddSceneFromFileCmd> *get_scene_from_file_cmds();
        // I think its wrong approach for how command execution should work but i think you'll get the point.
};

And also we create system that should execute all commands in some system:

void scene_tree_manager_system(
                                        World* p_world
                                        SceneTreeCommandDatabag *p_cmds,
                                        ) 
{
	// Previous commands


	for (int i = 0; i < p_cmds.get_scene_from_file_cmds()->size(); i++)
	{
              // Add new scenes
        }
        // Clear LocalVector<AddSceneFromFileCmd>


	// Next commands
}

So, every time when you want to create new node you can just add scene_tree_manager_system() in your system Pipeline, get SceneTreeCommandDatabag inside your system and just call function that add your command in databag.

I like this idea!

We can't execute SceneTree commands in multi-thread because you have access to nodes, and from there you can do anything you want with it. Godex is not aware of what you do with the node, and so it's not able to pre-build a pipeline that can run in MT, so run that systems in multi-thread is dangerous. Btw, this is the exact reason why you can't multi-thread scripts, since you don't really know the data you will access, and usually you have to rely on things like the mutex: for this reason is much more optimal not multithread the scripts at all.

You proposed the command queue to fix these issues, and so be able to run the systems that submit the ScenTree commands in MT. However, I doubt it will improve performances running those systems in MT: since the heavy lifting is executed in single thread anyway.

For me execute those systems in single thread is a good thing. In this way we can do the exact same thing we would do in a script: so we have 0 constraints.
From a system we may:

  • Spawn nodes.
  • Change the node values.
  • Load new scenes.
  • Move the node to a specific location.

We can create a databag that has the proposed functions, to fetch the nodes, and so execute the job directly.

For example:

void my_system(SceneTreeDatabag *p_scene_tree) {
	Node* node = p_scene_tree.get_node("./my/node/path");
	Node* scene = p_scene_tree.load_scene("res://my/scene/path.tres");
	node->add_child(scene);
}

and we can even introduce get_node that execute the cast to a specific type:

void my_system(SceneTreeDatabag *p_scene_tree) {
	RigidBody* rigid_body = p_scene_tree.get_node<RigidBody>("./my/node/path");
	rigid_body->set_collision_layer(1);
}

I really like this approach: for complex things you can just use ECS, while when you need to manipulate the SceneTree (for example to update the UI) you can just use a system. Those systems will not be faster or slower than doing it in the Godot way, for obvious reasons (and to me this is a +), but definitely there is the architectural benefit (another +).


The Systems that fetch the World are always run in single threat, because of the nature of the World that give access to anything.

We can do the same for SceneTreeDatabag, or we can consider to introduce the proposed functions directly inside the World.


This is a really great idea, and I'm thankful you had that!
What do you think about all?

In chat we agreed on adding the SceneTreeDatabag that provides the functionalities to directly deal with the tree and so directly modify the nodes.

Implemented by: #163