Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
102 lines (73 sloc) 5.99 KB

2nd August 2019

This devlog mainly explains some of the problems I reported last time and how we managed to get to a solution eventually.

This was easily the biggest challenge we faced while designing the addon and Editor framework for VCS because the working of GDNative C++ turned out to be something different that what we understood earlier. Thanks to the help from karroffel, and all the help we got at #godotengine-devel, we were able to finalise on an architecture.

1. Overriding API Functions from the addon and replacing the Editor VCS Interface class instance in the editor

Expectations

Earlier we expected us to create an engine Editor VCS Interface class that when extended in GDNative, we then would require the GDNative addon to override some of the API functions that provide data to the editor, and set the singleton variable of the Editor VCS Interface class to the child object. This was decided assuming that the class layout of GDNative bindings and the engine class is similar.

Counters

  1. GDNative is essentially a Script. And since a GDNative addon is a Script Instance, thus there wasn't any possibility of replacing singleton instances of the engine editor from Script instances, because Scripts are not made for this use case.
  2. Static Functions/static members/function pointers are not registered in the serialised output needed to generate our custom GDNative C++ bindings, which makes things like setting singleton instances from Scripts, or sending any data directly to the engine editor class instances directly virtually impossible.
  3. Inheritance of GDNative C++ classes is not entirely the same as pure C++ inheritance from the classes existing in Godot Editor's source. It is more based on composition at reported by karroffel.
  4. ClassDB does not store static variables/functions and doesn't have the utility to store function pointers.

Mitigations

There were no possible mitigations around this. The disability to access any static functions stopped this approach.

2. Using an API struct object to pass function pointers to call from the editor

Immediately after seeing this being done in the AR/VR GDNative plugins, we knew that implementing something like that would be overkill considering the amount of backend magic we would require to accomplish this sort of a workflow. In the end we used something similar but we regarded using the engine features to be cleaner instead of implementing it from scratch.

3. Calling methods on the inherited object of Editor VCS Interface (what we solved half the problem with)

Expectations

We expected to be able to use ClassDB::get_inheriters_from_class() with the argument of EditorVCS Interface, to be able to get the name of the GDNative class defined in our GDNative addon. Next we wanted to use Variant::construct_from_string() to create an instance of the class defined in the addon. This addon should define some methods which we would ->call() to get the returned data from VCS APIs like libgit2 for Git and others.

Counters

  1. ClassDB doesn't store class information coming from GDNative addons

Mitigations

We had to find a different way to instantiate the addon. After we learned that Script resources are accessible through ScriptServer, we came up with an architecture as follows:

  • Editor VCS Interface would now act as a proxy for the editor to talk to the actual API implemented in GDNative. It implements functions similar to String get_vcs_name() which directly return the required data types and not Variant objects as see had earlier.

It looks a bit similar to the code block below:

class EditorVCSInterface : public Object {

  GDCLASS(EditorVCSInterface, Object)
  
  // End points of our proxy functions
  Variant _get_vcs_name() { return String(); }
  /* This is called only when the addon has not  
  /* registered a function by the name "_get_vcs_name" */

protected:
  static void bind_methods();

public:
  static EditorVCSInterface *get_singleton();
  
  virtual String get_vcs_name();
  .
  .
  .
}

get_vcs_name() is the proxy function that the editor can call:

String EditorVCSInterface::get_vcs_name() {

	return call("_get_vcs_name"); // Notice the underscore. This is calling the proxy end-point, which should be defined in the addon, but it defaults to our default implementation if the function is not available in the addon.
}

Here _bind_methods() helps us redirect calls to the proxy end points.

void EditorVCSInterface::_bind_methods() {

	ClassDB::bind_method(D_METHOD("_get_vcs_name"), &EditorVCSInterface::_get_vcs_name);
}

Advantages

  1. Editor doesn't need to cast from Variant objects to its desired types.
  2. Our GDNative addon can skip implementing some functionalities of our API. This allows greater flexibility for implementing different VCS APIs. (A BIG plus in this method)

Instantiation of the Script Instance

The VersionControlEditorPlugin, which is an Editor Plugin that handles all things related to VCS, instantiates a EditorVCSInterface object after creating a GDNative Script instance successfully and attaches the addon script instance to the EditorVCSInterface object.

We tried a bunch a different methods for detecting the addon from the editor but eventually we ended up with using ScriptServer to find the GDNative addons, instead of from the ClassDB.

// Inside the editor plugin
  String path = ScriptServer::get_global_class_path(selected_addon);
	Ref<Script> script = ResourceLoader::load(path);
	
	EditorVCSInterface *vcs_interface = memnew(EditorVCSInterface);
	ScriptInstance *addon_script_instance = script->instance_create(vcs_interface);
	
	vcs_interface->set_script_and_instance(script.get_ref_ptr(), addon_script_instance);
  
  // Now we can use the interface api directly from the editor!
  vcs_interface->get_vcs_name();

Hence we have now set down this architecture in code and building on top of this is the only way up now. Hopefully we don't see any more problems like these coming up any time soon.

Thank you for making it this far :)

Cheers

IronicallySerious

You can’t perform that action at this time.