Summary of Thrive and Leviathan codebase

I feel very lost whenever I try to do something with Thrive (or Leviathan)'s code. I would like a summary of how Thrive and Leviathan work overall, if at all possible.

1 Like

I shall try…

So first off the GameWorld derived type, in the cell stage it is called CellStageWorld, is the place where a lot of the stuff happens. It is made according to (mostly):


It’s a bit unorthodox to have the different Component types have methods in them, but oh well, it is what it is.

So we have the CellStageWorld that has a bunch of Systems in it, these systems work on Components that for example are the Position or RenderNode, or a thrive specific one is MembraneComponent. So the systems run on each game update on either a list of some Components or a combination of some components. In the case of running on a combination of components, the system is ran on only entities that have all of the needed components. Additionally the scripts in AngelScript can register Systems and Components as well. So that is the world stuff.

The world also contains the PhysicalWorld that handles the physics. Entities that have a Physics component in them will have physics on them, after a PhysicsBody is created from a PhysicsShape that defines the shape of the physics body. Whenever the physics is ran and some object is moved, it reports the new data to Position component. One more thing about the physics is that it is possible to create physical materials. The physical materials can be used to create pairs and callbacks can be added to the pairs. So that when two physical objects collide that match a physical material pair, the physics callback for that pair is called. In Thrive these physics callbacks basically call a script function to determine what should happen with the collision and for example apply the pilus hit damage.

Then there is the GUI that is currently done using CEF. It loads a HTML page like a web process and that result is rendered on top of the entities rendered from a GameWorld. The GUI can communicate two ways with the rest of the game: by registering or sending events, or by having native functions exposed. The first method is the easier one requiring no difficult coding, just registering an event listener and firing an event. The second method needs editing the thrive_v8_extension.js as well as the C++ code that receives those function calls ThriveJSHandler::Execute. The C++ side is additionally run in a separate process, so it needs to send a message to the main process that is then received by ThriveJSMessageHandler::OnProcessMessageReceived. So TLDR for communicating with the GUI: just use event sending. I do plan on replacing the GUI with a library of my own that will use AS for scripting to make it much, much easier.

As a final thing I guess I could also talk about how the C++ <-> AngelScript stuff works.
So first the game registers the application interface (https://www.angelcode.com/angelscript/sdk/docs/manual/doc_register_api_topic.html) which are the classes as well as functions and methods the scripts are allowed to call. The AS manual has a lot of good examples in it. But I’ll just quickly mention that most method registering can be converted to the equivalent AS syntax quite easily. The only big differences are the AS handle types, the reference types (AS has &in and &out references), and the string type is named just string in AS. Often when dealing with handles wrappers need to be written that take in a Type* and wrap it as a reference counted pointer: Type::WrapPtr<Type>(variable) that will automatically handle releasing the incremented reference that AS gave us and the resulting smart pointer is then good to pass to C++ methods taking in a Type::pointer.
Reference counting is needed because it guarantees the scripts can’t access deleted objects which would be bad. Though Component types are not reference counted as I thought it might impact performance too much.
An example of this is the Patch and PatchMap classes: https://github.com/Revolutionary-Games/Thrive/blob/master/src/microbe_stage/patch.h and the registration is here: https://github.com/Revolutionary-Games/Thrive/blob/master/src/scripting/register_patches.cpp

Script execution works by calling methods on ScriptExecutor which then handles starting the AS execution. While executing the AS code can call any registered things in the application interface. For example:

auto returned =
    ThriveCommon::get()->getMicrobeScripts()->ExecuteOnModule<bool>(
        setup, false, gameWorld, shape1, obj1, shape2, obj2,
        contactPoint.m_index0, contactPoint.m_index1,
        std::abs(static_cast<float>(contactPoint.getDistance())),
        appliedEffect);

That code is from: https://github.com/Revolutionary-Games/Thrive/blob/0895ce01bddd520015cf0500723cbaa0513c7575/src/thrive_common.cpp#L271

With template magic the call to the script automatically handles reference counting for types needing it as long as it is passed in as a pointer, and also handles other class types registered to scripts and literal types. And it allows even returning a reference counted object type from the scripts as a return value from the script function.

I hope that made at least some sense…

Here’s an image I spent quite a bit of time on (it is using UML 2 notation):

Hopefully this is helpful, it’s a bit difficult to just describe everything.

Edit: fixed some typos and added examples

6 Likes

What would you say is the benefit to using the leviathan engine as opposed to an engine which is more traditionally programmer-friendly, such as perhaps Unity?

One of the issues with Unity is that it is not open source. There has been some talk about moving to Godot, which is open source, but so far from what I have seen it does not seem likely.

2 Likes

Just the fact that moving requires rewriting the entire game. And we will eventually end up with a bunch of our own code anyway for thrive with any engine to make it difficult to get into it. I think this thread has all the reasoning behind the engine choice: https://forum.revolutionarygamesstudio.com/t/switching-engines/340

Though, if I had a time machine, we would have started on a godot version in 2017. Now with all of the “heavy” programming falling on me, I doubt I would have the energy to do a full rewrite, and the current engine situation is not hindering me at all.

3 Likes
So you haven’t found anyone who could and would help you with the engine stuff?

We’ve barely found programmers to write some code for Thrive…

1 Like