Overview

Note

Prefer learning by example? Jump straight to the examples! Looking for a specific class or method? Check the class index

Object types and their relationships

The Logic Engine consists of a network of Lua scripts with a set of inputs and outputs with links between them. A special type of object we call binding serves as a bridge to a Ramses scene.

The following graph shows an example of such script network:

_images/overview.svg

The scripts have inputs and outputs which together define the script’s interface (more info on scripts). Scripts can be linked together directionally, so that the output of a script can provide its data to the input of another script (more info on links). Scripts can’t interact with Ramses objects directly. Instead, they can link to Bindings which are designed to “bind” Ramses objects and modify their properties’ values (node visibility, transformation values, material properties etc.) (more info on bindings).

The greyed-out slots in the image above represent input properties which are neither linked nor have a statically configured value. In bindings, this denotes that the corresponding Ramses property is not being updated by the Logic Engine (see also next section). In scripts, these properties will receive a default value at runtime (0, 0.0f, “”, true etc.) unless explicitly set by the application logic. Usually, script inputs without a configured value or a link to other output are considered the interface of the logic network towards a runtime application, and their values are supposed to be explicitly set at runtime.

Note

One of the planned features of the Logic Engine is to formalize interface inputs in a special interface class in a future release.

Data flow

The cornerstone of the Logic Engine is the rlogic::LogicEngine::update() method which “executes” the script network and updates the values of the Ramses scene bound to it. The nodes are executed based on a topological graph sort, where the traversal direction is given by the link pairs (A, B) where A is an output and B is an input property in the logic graph (as shown here).

In order to save performance, logic nodes are not executed again if none of their input values changed since the last update. Bindings are an exception to this rule - setting a value of a binding input always results in the value of that input being passed further to the bound Ramses object, regardless if the corresponding value in Ramses is the same or not. This can be useful when you want to force re-apply the value to Ramses e.g. you can have your own logic to control the visibility of all Ramses nodes, and only use Logic Engine to control transformation properties. In that case you should never set the “visibility” property of a Binding object, instead set the visibility directly on the bound ramses::Node.

Warning

We strongly discourage setting values to Ramses objects and to Ramses Logic bindings in the same update cycle to avoid unexpected behavior. At any given time, use one or the other, not both mechanisms to set values!

The execution logic is slightly different for scripts and bindings. Scripts will set the values of their outputs based on the logic in their run() function, which may set all, a subset or none of the script’s outputs. Bindings execution logic processes each of its input slots individually and checks if a value was explicitly set on it (either from user code or from an incoming link). Only those inputs which have such value are also set on the bound Ramses object.

Save/load to file

The Logic Engine is designed to be serialized and deserialized into binary files for fast loading. The first call to rlogic::LogicEngine::update() after loading from file will execute all scripts. Bindings will only be executed if some or all of their inputs are linked to a script output. Binding values will only be passed further to Ramses if their values were modified, e.g. by a link which produced a different value than before saving, or if the application called rlogic::Property::set() explicitly on any of the bindings’ input properties

Script creation

The entry point to RAMSES logic is a factory-style class rlogic::LogicEngine which can create instances of all other types of objects supported by RAMSES Logic:

You can create multiple instances of rlogic::LogicEngine, but each copy owns the objects it created, and must be used to destroy them, as befits a factory class.

You can create scripts using the rlogic::LogicEngine class like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "ramses-logic/LogicEngine.h"

using namespace ramses::logic;

std::string source = R"(
    function interface()
        IN.gear = INT
        OUT.speed = FLOAT
    end

    function run()
        OUT.speed = IN.gear * 15
    end
)"

LogicEngine engine;
LuaScript* script = engine.createLuaScriptFromSource(source, "simple script");
script->getInputs()->getChild("gear")->set<int32_t>(4);

script->execute();
float speed = script->getOutputs()->getChild("speed")->get<float>();
std::cout << "OUT.speed == " << speed;

The syntax required in all scripts is plain Lua (version 5.1) with the special addition of the special interface() and run() functions which every script needs to have. The interface() function is the place where script inputs and outputs are declared using the special global objects IN and OUT. The run() function can access the values of inputs and may set the values of outputs using the IN and OUT globals respectively.

Even though the IN and OUT objects are accessible in both interface() and run() functions, they have different semantics in each function. The interface function only declares the interface of the script, thus properties declared there can only have a type, they don’t have a value yet - similar to function signatures in programming languages.

In contrast to the interface() function, the run() function can’t declare new properties any more, but the properties have a value which can be read and written.

The interface() function is only ever executed once - during the creation of the script. The run() function is executed every time one or more of the values in IN changes, either when the C++ application changed them explicitly, or when any of the inputs is linked to another script’s output whose value changed.

For more information about Lua scripts, refer to the rlogic::LuaScript class documentation.

The interface() method enforces static types on the script’s interface, while Lua is a dynamically typed language. This is meant to make it easier and less error-prone to integrate Lua scripts with C++/Java code, which are otherwise statically typed languages. Note that in the example above, the C++ code used get<T> and set<T> to interact with the scripts. The underlying rlogic::Property class also has a rlogic::Property::getType() method which provides the type of the property as an enum value.

See also the section on Lua syntax specifics imposed by the Logic Engine.

You can link scripts to form a more sophisticated logic execution graph.

You can bind to Ramses objects to control a 3D Ramses scene.

Finally, the rlogic::LogicEngine class and all its content can be also saved/loaded from a file. Refer to the section on saving/loading from files for more details.

Object lifecycle

All objects besides the rlogic::LogicEngine instance follow a strict factory pattern. An object X is created by a method of the shape X* LogicEngine::createX(...). The pointer returned shall not be freed or deleted, instead objects must be destroyed by calling rlogic::LogicEngine::destroy().

Note

This may seem strange for a library which is based on C++17, but there are good reasons for this design choice. Smart pointers don’t work well together with Windows DLL’s, specifically when combining different CRTs. In order to provide a stable API on Windows we chose to use raw pointers and hide object creation/deletion behind a pimpl/factory pattern.

The rlogic::LogicEngine doesn’t create or destroy objects on its own - all data is explicitly created by calling create and destroy methods. There are two special cases worth mentioning:

Note

Loading data from files will invalidate all previous pointers to objects in the rlogic::LogicEngine. To avoid that, we recommend generally avoiding using a logicengine instance which already has content to load from files, and instead always create a fresh instance.

Indexing inside and outside Lua

Lua has traditionally used indexing starting at 1, countrary to other popular script or programming languages. Thus, the syntax and type checks of the Ramses Logic runtime honours standard indexing used in Lua (starting by 1). This allows for example to use Lua tables as initializer lists for arrays, without having to provide indices. Take a look at the following code sample:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function interface()
    OUT.array = ARRAY(2, INT)
end

function run()
    -- This will work
    OUT.array = {11, 12}
    -- This will also work
    OUT.array = {
        [1] = 11,
        [2] = 12
    }
    -- This will not work and will result in error
    OUT.array = {
        [0] = 11,
        [1] = 12
    }
end

The first two snippets are equivalent and will work. The first syntax (line 7) is obviously most convenient - just provide all array elements in the Lua table. Note that Lua will implicitly index elements starting from 1 with this syntax. The second syntax (line 9-12) is equivalent to the first one, but explicitly sets table indices. The third syntax (line 14-17) is the one which feels intuitive for C/C++ developers, but will result in errors inside Lua scripts.

In order to achieve memory efficiency, but also to be consistent with C/C++ rules, the C++ API of Ramses Logic provides index access starting from 0 (for example rlogic::Property::getChild()). The index mapping is taken over by the Ramses Logic library.

Linking scripts to Ramses scenes

Lua scripts would not make much sense on their own if they can’t interact with Ramses scene objects. The way to link script output properties to Ramses scene objects is by creating rlogic::RamsesBinding instances and linking their inputs to scripts’ outputs. There are different binding types depending on the type of Ramses object - refer to rlogic::RamsesBinding for the full list of derived classes. Bindings can be linked in the exact same way as scripts can. In fact, they derive from the same base class - rlogic::LogicNode. The only difference is that the bindings have only input properties (the outputs are implicitly defined and statically linked to the Ramses objects attached to them), whereas scripts have inputs and outputs explicitly defined in the script interface.

One might wonder, why not allow to directly link script outputs to Ramses objects? The reason for that is two-fold:

Note

Bindings are ‘updated’ slightly differently than rlogic::LuaScript. A script will be executed if and only if any of its input values have changed since last time it was executed. A rlogic::RamsesBinding in contrast will also check if its input properties have a value which was explicitly set by the user, either by direct rlogic::Property::set() or indirectly with a rlogic::LogicEngine::link(). This way, only intended changes are passed further to Ramses objects and you can mix and match different control systems. For example, you can use script logic to control the transformation properties of nodes, but have your own logic/code for controlling the visibility or resource loading.

If you destroy a LogicNode calling rlogic::LogicEngine::destroy(), all links from and to this rlogic::LogicNode will be automatically removed.

What happens on update

The Logic Engine manages a network of Logic Nodes of various types which are executed in every update loop (as triggered by rlogic::LogicEngine::update()). But what happens inside each update loop?

First, all rlogic::LogicNode objects are ordered based on the links between them (as described in the section explaining links). Then, each of the nodes is executed according to that order, unless it does not need to be executed. A node needs to be executed if:

  • it was never executed before, i.e.:

    • it was just created

    • it was loaded from a file

  • it was executed in a previous update loop, but the value of at least one of its inputs changed. The cause of such change can either be:

    • Setting the value directly over rlogic::Property::set(), or…

    • An output of another rlogic::LogicNode which changed in the same update loop is linked to this input or…

    • A link to this input was just created

In other words, the network of logic nodes is traversed along the links between them, the values of nodes’ outputs is carried over to the linked nodes’ inputs, and if no input changed for a given node, its execution is skipped to save CPU cycles.

The execution logic is different for each type of rlogic::LogicNode. rlogic::LuaScript instances execute their run() method while rlogic::RamsesBinding set the values of bound Ramses objects. For more details, consult the documentation of the specific classes.

Performance and caching

From performance point of view, it is not necessary to execute each rlogic::LogicNode in an update step. LogicNodes with not changed inputs since the last update step, can be omitted. This is because the outputs of this rlogic::LogicNode should not change if another update will be executed. There is one exception to this rule. If a rlogic::LogicNode has not been updated before, it will always be updated, no matter whether inputs are changed or not. As a user you usally don’t have to care about this mechanism, you just should know that LogicNode are only executed if at least one input is changed.

Error handling

Some of the RAMSES Logic classes’ methods can issue errors when used incorrectly or when a Lua script encounters a compile-time or run-time error. Those errors are globally collected by the rlogic::LogicEngine class and can be obtained by calling rlogic::LogicEngine::getErrors(). Beware that any of the mutable methods of rlogic::LogicEngine clear the previously generated errors in the list, so that the list only ever contains the errors since the last method call.

For code samples which demonstrate how compile-time and runtime errors can be gracefully handled, have a look at the examples.

Using Lua modules

The Logic Engine restricts which Lua modules can be used to a subset of the standard modules of Lua 5.1:

  • Base library

  • String

  • Table

  • Math

  • Debug

For more information on the standard modules, refer to the official Lua documentation of the standard modules.

Some of the standard modules are deliberately not supported:

  • Security/safety concerns (loading files, getting OS/environment info)

  • Not supported on all platforms (e.g. Android forbids direct file access)

  • Stability/integration concerns (e.g. opening relative files in Lua makes the scripts non-relocatable)

Iterating over object collections

Iterating over objects can be useful, for example when loading content from files or when applying search or filter algorithms over all objects from a specific type. The rlogic::LogicEngine class provides iterator-style access to all of its objects:

1
2
3
4
5
6
7
LogicEngine logicEngine;
Collection<LuaScript> allScripts = logicEngine.scripts();

for(const auto script : allScripts)
{
    std::cout << "Script name: " << script->getName() << std::endl;
}

The rlogic::Collection class and the iterators it returns are STL-compatible, meaning that you can use them with any other STL algorithms or libraries which adhere to STL principles. The iterators implement forward iterator semantics (have a look at C++ docs).

Note

The rlogic::Iterator and rlogic::Collection classes are not following the pimpl pattern as the rest of the Ramses Logic to performance ends. Be careful not to depend on any internals of the classes (mostly the Internally wrapped STL containers) to avoid compatibility problems when updating the Ramses Logic version!

Saving/Loading from file

The rlogic::LogicEngine class and its content can be stored in a file and loaded from file again using the functions rlogic::LogicEngine::saveToFile() and rlogic::LogicEngine::loadFromFile(). The latter has an optional argument to provide a Ramses scene which should be used to resolve references to Ramses objects in the Logic Engine file. Read further for more details.

Note

Even though it would be technically possible to combine the storing and loading of Ramses scenes together with the Logic Engine and its scripts in a single file, we decided to not do this but instead keep the content in separate files and load/save it independently. This allows to have the same Ramses scene stored multiple times or with different settings, but using the same logic content, as well as the other way around - having different logic implementations based on the same Ramses scene. It also leaves more freedom to choose how to store the Ramses scene. This implies that at most a single Ramses scene can be referenced at the time of saving, having more than one scene will result in error.

Object lifecycle when saving and loading to files

After loading, the current state of the logic engine objects will be completely overwritten by the contents from the file. If you don’t want this behavior, use two different instances of the class - one dedicated for loading from files and nothing else.

Here is a simple example which demonstrates how saving/loading from file works in the simplest case (i.e. no references to Ramses objects):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// Creates an empty LogicEngine instance, saves it to file and destroys the object
{
    rlogic::LogicEngine engine;
    engine.saveToFile("logicEngine.bin");
}
// Loads the file we saved above into a freshly created LogicEngine instance
{
    rlogic::LogicEngine engine;
    engine.loadFromFile("logicEngine.bin");
}

After the call to rlogic::LogicEngine::loadFromFile() successfully returns (refer to the Error handling section for info on handling errors), the state of the rlogic::LogicEngine class will be overwritten with the contents loaded from the file. This implies that all objects created prior loading will be deleted and pointers to them will be pointing to invalid memory locations. We advise designing your object lifecycles around this and immediately dispose such pointers after loading from file.

Warning

In case of error during loading the rlogic::LogicEngine may be left in an inconsistent state. In the future we may implement graceful handling of deserialization errors, but for now we suggest discarding a rlogic::LogicEngine object which failed to load.

Saving and loading together with a Ramses scene

In a slightly less simple, but more realistic setup, the Logic Engine will contain objects of type Ramses<Object>Binding which contain references to Ramses objects. In that case, use the optional ramses::Scene* argument to rlogic::LogicEngine::loadFromFile() to specify the scene from which the references to Ramses objects should be resolved. Ramses Logic uses the getSceneObjectId() method of the ramses::SceneObject class to track references to scene objects. This implies that those IDs must be the same after loading, otherwise rlogic::LogicEngine::loadFromFile() will report error and fail. Ramses Logic makes no assumptions on the origin of the scene, its name or ID.

For a full-fledged example, have a look at the serialization example.

Warning

The LogicEngine expects that immediately after loading, the state of the Ramses scene is the same as it was right before saving, and will not modify Ramses objects which are attached to bindings in the LogicEngine in its first update, unless they are linked to scripts or explicitly overwritten by rlogic::Property::set() calls after loading from the file. We strongly advice to always save and load both the Ramses scene and the LogicEngine scene together to avoid data inconsistencies!

Using memory buffer instead of file

You can use rlogic::LogicEngine::loadFromBuffer() to load the contents of the logic engine from your own memory. This can be useful if you have your own file management logic, or the data comes from a different source than a file on disk. Be mindful that passing data buffers over the boundaries of libraries can be unsafe with C++, and some errors/abuse can’t be reliably prevented. Make sure you check the size of the buffer and don’t load from memory of untrusted origins.

Additional Lua syntax specifics

RAMSES Logic fuses C++ and Lua code which are quite different, both in terms of language semantics, type system and memory management. This is mostly transparent to the user, but there are some noteworthy special cases worth explaining.

Vec2/3/4 types

While the property types which reflect Lua built-in types (BOOL, INT, FLOAT, STRING) inherit the standard Lua value semantics, the more complex types (VEC2/3/4/I/F) have no representation in Lua, and are wrapped as Lua tables. They have the additional constraint that all values must be set simultaneously. It’s not possible for example to set just one component of a VEC3F - all three must be set at once. The reason for this design decision is to ensure consistent behavior when propagating these values - for example when setting Ramses node properties or uniforms.

The global IN and OUT objects

The IN and OUT objects are global Lua variables accessible anywhere. They are so-called user types, meaning that the logic to deal with them is in C++ code, not in Lua. This means that any kind of error which is not strictly a Lua syntax error will be handled in C++ code. For example, assigning a boolean value to a variable which was declared of string type is valid in Lua, but will cause a type error when using RAMSES Logic. This is intended and desired, however the Lua VM will not know where this error comes from other than “somewhere from within the overloaded C++ code”. This, stack traces look something like this when such errors happen:

lua: error: Assigning boolean to string output 'my_property'!
stack traceback:
    [C]: in ?
    [string \"MyScript\"]:3: in function <[string \"MyScript\"]:2>

The top line in this stack is to be interpreted like this:

  • The error happened somewhere in the C code (remember, Lua is based on C, not on C++)

  • The function where the error happened is not known (?) - Lua doesn’t know the name of the function

The rest of the information is coming from Lua, thus it makes more sense - the printed error message originates from C++ code, but is passed to the Lua VM as a verbose error. The lower parts of the stack trace are coming from Lua source code and Lua knows where that code is.

Furthermore, assigning any other value to the IN and OUT globals is perfectly fine in Lua, but will result in unexpected behavior. The C++ runtime will have no way of knowing that this happened, and will not receive any notification that something is being written in the newly created objects.

Things you should never do

There are other things which will result in undefined behavior, and RAMSES Logic has no way of capturing this and reporting errors. Here is a list:

  • Assign IN directly to OUT. This will not have the effect you expect (assigning values), but instead it will set the OUT label to point to the IN object, essentialy yielding two pointers to the same object - the IN object. If you want to be able to assign all input values to all output values, put them in a struct and assign the struct, e.g.:

function interface()
    IN.struct = {}
    OUT.struct = {} -- must have the exact same properties as IN.struct!!
end

function run()
    -- This will work!
    OUT.struct = IN.struct
    -- This will not work!
    OUT = IN
end
  • Do anything with IN and OUT in the global script scope - these objects don’t exist there. However, you can pass IN and OUT as arguments to other functions, but consider Special case: using OUT object in other functions

  • Calling the interface() method from within the run() method or vice-versa

  • Using recursion in the interface() or run() methods

  • Overwriting the IN and OUT objects. Exception to this is assigning OUT = IN in the run() method

  • using threads or coroutines. We might add this in future, but for now - don’t use them

Things you should avoid if possible

Even though it is not strictly prohibited, it is not advised to store and read global variables inside the run() function, as this introduces a side effect and makes the script more vulnerable to errors. Instead, design the script so that it needs only be executed if the values of any of the inputs changed - similar to how functional programming works. rlogic::LogicNode provides an interface to access the inputs and outputs declared by the interface() function - see rlogic::LogicNode::getInputs() and rlogic::LogicNode::getOutputs().

Special case: using OUT object in other functions

It is possible to pass the OUT struct from the run() function to a different function to set the output values. But be aware that not all constellations are working. Here are some examples to explain the working variants:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function interface()
    OUT.param1 = INT
    OUT.struct1 = {
        param2 = INT
    }
end

function setParam1(out)
    out.param1 = 42 -- OK
end

function setDirect(p)
    p = 42 -- NOT OK: Will create local variable "p" with value 42
end

function setStruct(struct)
    struct.param2 = 42 -- OK
    struct = {
        param2 = 42 -- NOT OK: Will create local variable "struct" with table
    }
end

function run()
    setParam1(OUT)
    setDirect(OUT.param1)
    setStruct(OUT.struct1)
end

As the above example demonstrates, passing objects to functions in Lua is done by reference. However, whenever the reference is overwritten with something else, this has no effect on the object which was passed from outside, but only lets the local copy of the reference point to a different value.

Logging

Internally there are four log levels available.

  • Info

  • Debug

  • Warn

  • Error

By default all internal logging messages are sent to std::cout. You can toggle this with rlogic::Logger::SetDefaultLogging(). In addition, it is possible to have a custom log handler function which is called each time a log message is issued.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <iostream>

Logger::SetLogHandler([](ElogMessageType msgType, std::string_view message){
    switch(type)
    {
        case ELogMessageType::ERROR:
            std::cout << "Error: " << message << std::endl;
            break;
        default:
            std::cout << message << std::endl;
            break;
    }
});

Inside the log handler function, you get the type of the message and the message itself as a std::string_view. Keep in mind, that you can’t store the std::string_view. It will be invalid after the call to the log handler function. If you need the message for later usage, store it in a std::string.

The amount of logging can be configured with rlogic::Logger::SetLogVerbosity(). This affects both the default logging and the custom logger.

Security and memory safety

One of the biggest challenges of modern C++ is finding a balance between compatibility with older compilers and platforms, while not sacrificing memory safety and code readibility. In the RAMSES ecosystem we try to find a good balance by testing with different compilers, employing automation techniques and making use of modern compiler-based tools to perform static code analysis and introspection. The methods and tools we use are:

  • compiling on different compilers (MSVC, gcc, clang) with strict compiler settings

  • clang-tidy with fairly strict configuration

  • valgrind

  • treat warnings as errors

  • use various clang-based sanitizers (undefined behavior, thread sanitizer, address sanitizer)

Those tools cover a lot of the standard sources of problems with C++ revolving around memory. We also uphold a strict code review, ensuring that each line of code is looked at by at least two pairs of eyes, for critical parts of the code usually more than that. Still, no project is safe from bugs, thus we recommend following some or all of the additional conventions and best practices from below subchapters to minimize the risk of memory-related bugs and malicious attacks when using Ramses Logic.

Additional memory safety measures

One of the biggest sources of bugs and security problems in C++ arise from memory management, both in terms of allocation/deallocation and memory access and boundary checks. Ramses Logic takes care of memory lifecycle for all objects created by it, and provides raw pointer access to their memory. We suggest creating your own wrapper objects for anything created or loaded by the rlogic::LogicEngine class and ensure it is destroyed exactly once and only after not used any more.

Furthermore, pay special attention when passing strings as std::string_view to and from the Logic Engine as those may not be terminated by a 0 and may lead to out of bounds accesses when used by functions expecting 0-termination.

Additional security considerations

Lua is a script language, and as such provides great flexibility and expresiveness at the cost of more error potential and security risks compared to other techniques for describing logic. The Logic engine and the underlying sol library do a lot of error checking and prevents undefined behavior by executing faulty script code, but there are cases which can’t be checked.

To give one example, a script may overwrite the global variables IN or OUT from within script code because of the nature of Lua scripts. This can’t be automatically checked by the runtime without overloading the global Lua metatable and injecting every single assignment operation, which is too high a cost to avoid faulty scripts.

To avoid malicious or broken script, we suggest implementing an additional security mechanism on top of Ramses Logic which doesn’t allow execution of scripts of unknown origin. Also, build your code with errors in mind and force scripts into an automated testing process. We also advise to use hashsums and whitelisting techniques to only execute scripts which are tested and verified to be benign.

Sanitizing of files and buffers

Since the Logic Engine can deserialize itself from files and memory buffers, it opens possibilities for data corruption and truncation. To mitigate those risks, we use Flatbuffer’s “Verify” feature which checks the integrity of data, detects possible index-out-of-range issues and prevents binary data abuse. What it doesn’t check is whether the actual memory buffer size (passed in rlogic::LogicEngine::loadFromBuffer()) is consistent with the size provided by the user. The application must ensure that this size does not exceed the size of the actual memory!

Performance

The Logic Engine is designed to be fast and efficient, as long as the performance improvements are not made at cost of unreadable code. In order to be able to track and improve the runtime of the Logic Engine, we maintain a set of benchmarks based on the google-benchmark library. These benchmarks can be used to measure the time it takes for specific operations under different loads. We kindly ask our users and developers to report performance problems by creating a benchmark which describes the specific use-case which needs optimizing. Refer to the google-benchmark docs for hints how to design good benchmarks, to set the time measurement units, derive O-complexity, etc.

Class Index

Top-level API classes:

Base classes:

Iterators:

Free functions:

Type traits:

template<typename T>
class rlogic::Collection
#include <Collection.h>

A Collection which allows STL-style algorithm with forward semantics to be executed (e.g. find_if, for_each etc.). A Collection should not be instantiated directly, instead use one of the rlogic::LogicEngine methods:

See also rlogic::Iterator for more info on the iterator objects returned by begin and end.

Template parameters:

Public Types

using iterator = Iterator<T, internal_container_type, false>

The iterator type returned by begin() and end()

using const_iterator = Iterator<T, internal_container_type, true>

The iterator type returned by cbegin() and cend()

using value_type = typename iterator::value_type

The iterator type after dereferencing

using pointer = typename iterator::pointer

Iterator value pointer type

using reference = typename iterator::reference

Iterator value reference type

Public Functions

iterator begin()

Return an iterator to the start of the collection

Return

iterator to the start of the collection

const_iterator begin() const

Return an const iterator to the start of the collection

Return

const iterator to the start of the collection

iterator end()

Return an iterator to the end of the collection

Return

iterator to the end of the collection

const_iterator end() const

Return a const iterator to the end of the collection

Return

const iterator to the end of the collection

const_iterator cbegin() const

Return a const iterator to the start of the collection

Return

const iterator to the of the collection

const_iterator cend() const

Return a const iterator to the end of the collection

Return

const iterator to the end of the collection

Collection() = delete

Default constructor is deleted because a collection is supposed to provide read-only access to internal data of the rlogic::LogicEngine class, therefore it has to be obtained by calling methods of the rlogic::LogicEngine which return such collections, e.g. rlogic::LogicEngine::scripts()

~Collection() noexcept = default

Default destructor

Collection(const Collection &other) noexcept = default

Default copy constructor

Parameters
  • other: collection to copy

Collection &operator=(const Collection &other) noexcept = default

Default assignment operator

Parameters
  • other: collection to assign from

Collection(internal_container_type &container)

Internal constructor. Not supposed to be called from user code!

Parameters
  • container: internal container

Private Types

using internal_container_type = std::vector<std::unique_ptr<T>>

Internal container type. Not supposed to be used by user code in any way!

Private Members

std::reference_wrapper<internal_container_type> m_container

Internal reference to the container holding the actual data held by the collection.

template<typename T>
struct rlogic::IsPrimitiveProperty
#include <EPropertyType.h>

Type trait which can be used to check if a type is primitive or not. “primitive” in this context means the type can be used as a template for rlogic::Property::set() and rlogic::Property::get(), i.e. it has a value which can be directly set/obtained, as opposed to non-primitive types like structs or arrays which don’t have a singular settable value.

Public Static Attributes

const bool value = false
template<>
struct rlogic::IsPrimitiveProperty<bool>
#include <EPropertyType.h>

Public Static Attributes

const bool value = true
template<>
struct rlogic::IsPrimitiveProperty<float>
#include <EPropertyType.h>

Public Static Attributes

const bool value = true
template<>
struct rlogic::IsPrimitiveProperty<int>
#include <EPropertyType.h>

Public Static Attributes

const bool value = true
template<>
struct rlogic::IsPrimitiveProperty<std::string>
#include <EPropertyType.h>

Public Static Attributes

const bool value = true
template<>
struct rlogic::IsPrimitiveProperty<vec2f>
#include <EPropertyType.h>

Public Static Attributes

const bool value = true
template<>
struct rlogic::IsPrimitiveProperty<vec2i>
#include <EPropertyType.h>

Public Static Attributes

const bool value = true
template<>
struct rlogic::IsPrimitiveProperty<vec3f>
#include <EPropertyType.h>

Public Static Attributes

const bool value = true
template<>
struct rlogic::IsPrimitiveProperty<vec3i>
#include <EPropertyType.h>

Public Static Attributes

const bool value = true
template<>
struct rlogic::IsPrimitiveProperty<vec4f>
#include <EPropertyType.h>

Public Static Attributes

const bool value = true
template<>
struct rlogic::IsPrimitiveProperty<vec4i>
#include <EPropertyType.h>

Public Static Attributes

const bool value = true
template<typename T, typename internal_container, bool isConst>
class rlogic::Iterator
#include <Iterator.h>

An STL-style iterator for various object types (T) with forward-semantics. See also:

Template parameters:

  • T: the object type to iterate over, e.g. rlogic::LuaScript

  • internal_container: the internally wrapped container type. User code should NOT depend on this type to avoid API incompatibilities!

  • isConst: true for const-iterators, false for non-const iterators

Dereferencing the iterator yields a pointer of type T* or const T*, depending if the iterator is a const iterator. The class is STL-compatible, meaning that the public type declarations may safely be used.

Additional note: the Iterator class does not support swap() functionality as well as mutable assignments, because the internal data structures used to iterate over objects are not supposed to be changed by user code. The iterators are supposed to be used only in read-only scenarios. It is still possible to exercise write access on the pointers returned by the iterator, but it’s not possible to assign a new value to the pointer itself or to swap the pointers.

Public Types

using difference_type = typename internal_iterator::difference_type

Type traits as mandated by STL for custom iterators.

using value_type = typename internal_iterator::value_type

The iterator type after dereferencing

using pointer = typename internal_iterator::pointer

Iterator value pointer type

using reference = typename internal_iterator::reference

Iterator value reference type

using iterator_category = std::forward_iterator_tag

Iterator type (refer to STL documentation for more information)

Public Functions

maybe_const_T operator*() noexcept

Operator dereferencing. Returns const T* if template argument isConst == true, otherwise returns T*.

Return

T* or const T* pointer to iterable object, depending on iterator constness

maybe_const_T operator->() noexcept

Member forwarding operator. Translates to ‘(const T*)->method’ if template argument isConst == true, otherwise to ‘(T*)->method’.

Return

T* or const T* pointer to iterable object, depending on iterator constness

Iterator &operator++() noexcept

Pre-increment operator.

Return

reference to self

Iterator operator++(int) noexcept

Post-increment operator.

Return

a copy of self before the increment was performed

template<bool otherIsConst>
bool operator==(const Iterator<T, internal_container, otherIsConst> &other) const noexcept

Equality operator.

Return

true if the iterators point to the same object internally

Parameters
  • other: the other iterator to compare to

template<bool otherIsConst>
bool operator!=(const Iterator<T, internal_container, otherIsConst> &other) const noexcept

Inequality operator.

Return

true if the iterators point to different objects internally

Parameters
  • other: the other iterator to compare to

Iterator() noexcept = default

Default constructor.

template<bool otherIsConst, typename = std::enable_if_t<isConst && !otherIsConst>>
Iterator(const Iterator<T, internal_container, otherIsConst> &other) noexcept

Constructor which allows const-iterator to be constructed from a non-const iterator, but not the other way around

Parameters
  • other: iterator to construct from

template<bool otherIsConst, class = std::enable_if_t<isConst && !otherIsConst>>
Iterator &operator=(const Iterator<T, internal_container, otherIsConst> &other) noexcept

Assignment operator which allows const-iterator to be assigned from a non-const iterator, but not the other way around

Parameters
  • other: iterator to be assign from

Iterator(const Iterator&) noexcept = default

Default copy constructor. This is redundant to the template version above, but is considered good style.

Iterator &operator=(const Iterator&) noexcept = default

Default assignment operator. This is redundant to the template version above, but is considered good style.

~Iterator() noexcept = default

Default destructor.

Iterator(internal_iterator iter) noexcept

Internal constructor which should not be called by user code to avoid API dependencies. Use rlogic::Collection::begin() and rlogic::Collection::end() or their const-counterparts to obtain iterators to desired rlogic::LogicEngine objects.

Parameters
  • iter: internal iterator to be constructed from

Private Types

using const_iter = typename internal_container::const_iterator

Internal type trait (const iterator type)

using non_const_iter = typename internal_container::iterator

Internal type trait (non-const iterator type)

using internal_iterator = typename std::conditional<isConst, const_iter, non_const_iter>::type

Internal type trait (internally wrapped iterator type)

using maybe_const_T = typename std::conditional<isConst, const T*, T*>::type

Type returned when dereferencing the iterator (conditionally metaprogrammed for const-correctness when used inside const iterator)

Private Members

internal_iterator m_iterator = {}

Internal iterator. This implementation is not following the pimpl pattern for efficiency reasons.

Friends

friend class Iterator< T, internal_container, true >
friend class Iterator< T, internal_container, false >
class rlogic::LogicEngine
#include <LogicEngine.h>

Central object which creates and manages the lifecycle and execution of scripts, bindings, and all other objects supported by the Ramses Logic library. All objects created by this class’ methods must be destroyed with destroy!

  • Use the create[Type] methods to create various objects, use destroy() to delete them.

  • Use link and unlink to connect data properties between these objects

  • use update() to trigger the execution of all objects

Public Functions

LogicEngine() noexcept

Constructor of LogicEngine.

~LogicEngine() noexcept

Destructor of LogicEngine

Collection<LuaScript> scripts() const

Returns an iterable rlogic::Collection of all rlogic::LuaScript instances created by this LogicEngine.

Return

an iterable rlogic::Collection with all rlogic::LuaScript instances created by this LogicEngine

Collection<RamsesNodeBinding> ramsesNodeBindings() const

Returns an iterable rlogic::Collection of all rlogic::RamsesNodeBinding instances created by this LogicEngine.

Return

an iterable rlogic::Collection with all rlogic::RamsesNodeBinding instances created by this LogicEngine

Collection<RamsesAppearanceBinding> ramsesAppearanceBindings() const

Returns an iterable rlogic::Collection of all rlogic::RamsesAppearanceBinding instances created by this LogicEngine.

Return

an iterable rlogic::Collection with all rlogic::RamsesAppearanceBinding created by this LogicEngine

LuaScript *findScript(std::string_view name) const

Returns a pointer to the first occurrence of a script with a given name if such exists, and nullptr otherwise.

Return

a pointer to the script, or nullptr if none was found

Parameters
  • name: the name of the script to search for

RamsesNodeBinding *findNodeBinding(std::string_view name) const

Returns a pointer to the first occurrence of a node binding with a given name if such exists, and nullptr otherwise.

Return

a pointer to the node binding, or nullptr if none was found

Parameters
  • name: the name of the node binding to search for

RamsesAppearanceBinding *findAppearanceBinding(std::string_view name) const

Returns a pointer to the first occurrence of an appearance binding with a given name if such exists, and nullptr otherwise.

Return

a pointer to the appearance binding, or nullptr if none was found

Parameters
  • name: the name of the appearance binding to search for

LuaScript *createLuaScriptFromFile(std::string_view filename, std::string_view scriptName = "")

Creates a new rlogic::LuaScript from an existing Lua source file. Refer to the rlogic::LuaScript class documentation for requirements that Lua scripts must fulfill in order to be added to the LogicEngine.

Attention! This method clears all previous errors! See also docs of getErrors()

Return

a pointer to the created object or nullptr if something went wrong during creation. In that case, use getErrors() to obtain errors. The script can be destroyed by calling the destroy method

Parameters
  • filename: path to file from which to load the script source code

  • scriptName: name to assign to the script once it’s created

LuaScript *createLuaScriptFromSource(std::string_view source, std::string_view scriptName = "")

Creates a new Lua script from a source string. Refer to the rlogic::LuaScript class for requirements which Lua scripts must fulfill in order to be added to the LogicEngine.

Attention! This method clears all previous errors! See also docs of getErrors()

Return

a pointer to the created object or nullptr if something went wrong during creation. In that case, use getErrors() to obtain errors. The script can be destroyed by calling the destroy method

Parameters
  • source: the Lua source code

  • scriptName: name to assign to the script once it’s created

bool destroy(LogicNode &logicNode)

Destroys a rlogic::LogicNode instance. If any links are connected to this rlogic::LogicNode, they will be destroyed too. Note that after this call, the execution order of rlogic::LogicNode may change! See the docs of link and unlink for more information.

Attention! This method clears all previous errors! See also docs of getErrors()

Return

true if logicNode destroyed, false otherwise. Call getErrors() for error details upon failure.

Parameters
  • logicNode: the logic node instance to destroy

RamsesNodeBinding *createRamsesNodeBinding(std::string_view name = "")

Creates a new rlogic::RamsesNodeBinding which can be used to set the properties of a Ramses Node object.

Attention! This method clears all previous errors! See also docs of getErrors()

Return

a pointer to the created object or nullptr if something went wrong during creation. In that case, use getErrors() to obtain errors. The binding can be destroyed by calling the destroy method

Parameters

RamsesAppearanceBinding *createRamsesAppearanceBinding(std::string_view name = "")

Creates a new rlogic::RamsesAppearanceBinding which can be used to set the properties of a Ramses Appearance object.

Attention! This method clears all previous errors! See also docs of getErrors()

Return

a pointer to the created object or nullptr if something went wrong during creation. In that case, use getErrors() to obtain errors. The binding can be destroyed by calling the destroy method

Parameters

bool update()

Updates all rlogic::LogicNode’s which were created by this LogicEngine instance. The order in which rlogic::LogicNode’s are executed is determined by the links created between them (see link and unlink). rlogic::LogicNode’s which don’t have any links between then are executed in arbitrary order, but the order is always the same between two invocations of update without any calls to link or unlink between them. As an optimization rlogic::LogicNode’s are only updated, if at least one input of a rlogic::LogicNode has changed since the last call to update. If the links between logic nodes create a loop, this method will fail with an error and will not execute any of the logic nodes.

Attention! This method clears all previous errors! See also docs of getErrors()

Return

true if the update was successful, false otherwise In case of an error, use getErrors() to obtain errors.

bool link(const Property &sourceProperty, const Property &targetProperty)

Links a property of a rlogic::LogicNode to another rlogic::Property of another rlogic::LogicNode. After linking, calls to update will propagate the value of sourceProperty to the targetProperty. Creating links influences the order in which scripts are executed - if node A provides data to node B, then node A will be executed before node B.

The link() method will fail when:

Creating link loops will cause the next call to update() to fail with an error. Loops are directional, it is OK to have A->B, A->C and B->C, but is not OK to have A->B->C->A.

After calling link, the value of the targetProperty will not change until the next call to update. Creating and destroying links generally has no effect until update is called.

Attention! This method clears all previous errors! See also docs of getErrors()

Return

true if linking was successful, false otherwise. To get more detailed error information use getErrors()

Parameters
  • sourceProperty: the output property which will provide data for targetProperty

  • targetProperty: the target property which will receive its value from sourceProperty

bool unlink(const Property &sourceProperty, const Property &targetProperty)

Unlinks two properties which were linked with link. After a link is destroyed, calls to update will no longer propagate the output value from the sourceProperty to the input value of the targetProperty. The value of the targetProperty will remain as it was after the last call to update - it will not be restored to a default value or to any value which was set manually with calls to rlogic::Property::set().

Attention! This method clears all previous errors! See also docs of getErrors()

Return

true if unlinking was successful, false otherwise. To get more detailed error information use getErrors().

Parameters
  • sourceProperty: the output property which is currently linked to targetProperty

  • targetProperty: the property which will no longer receive the value from sourceProperty

bool isLinked(const LogicNode &logicNode) const

Checks if an input or output of a given LogicNode is linked to another LogicNode

Return

true if the given LogicNode is linked to any other LogicNode, false otherwise.

Parameters
  • logicNode: the node to check for linkage.

const std::vector<std::string> &getErrors() const

If an internal error occurs e.g. during loading or execution of a rlogic::LuaScript, a detailed error description can be retrieved with this method. The list of errors is reset before each LogicEngine method is called, and contains potential errors created by that method after it returns.

Return

a list of (potentially multi-line) strings with detailed error information. For Lua errors, an extra stack trace is contained in the error string. Error strings contain new line characters and spaces, and in the case of Lua errors it also contains the error stack trace captured by Lua.

bool saveToFile(std::string_view filename)

Writes the whole LogicEngine and all of its objects to a binary file with the given filename. The RAMSES scene potentially referenced by rlogic::RamsesBinding objects is not saved - that is left to the application. LogicEngine saves the references to those object, and restores them after loading. Thus, deleting Ramses objects which are being referenced from within the LogicEngine will result in errors if the Logic Engine is loaded from the file again. Note that it is not sufficient to have objects with the same name, they have to be the exact same objects as during saving! For more in-depth information regarding saving and loading, refer to the online documentation at https://ramses-logic.readthedocs.io/en/latest/api.html#save-load-to-file

Note: The method reports error and aborts if the rlogic::RamsesBinding objects reference more than one Ramses scene (this is acceptable during runtime, but not for saving to file).

Attention! This method clears all previous errors! See also docs of getErrors()

Return

true if saving was successful, false otherwise. To get more detailed error information use getErrors()

Parameters
  • filename: path to file to save the data (relative or absolute). The file will be created or overwritten if it exists!

bool loadFromFile(std::string_view filename, ramses::Scene *ramsesScene = nullptr, bool enableMemoryVerification = true)

Loads the whole LogicEngine data from the given file. See also saveToFile(). After loading, the previous state of the LogicEngine will be overwritten with the contents loaded from the file, i.e. all previously created objects (scripts, bindings, etc.) will be deleted and pointers to them will be invalid. The (optionally) provided ramsesScene will be used to resolve potential rlogic::RamsesBinding objects which point to Ramses objects. You can provide a nullptr if you know for sure that the LogicEngine loaded from the file has no rlogic::RamsesBinding objects which point to a Ramses scene object. Otherwise, the call to loadFromFile will fail with an error. In case of errors, the LogicEngine may be left in an inconsistent state. For more in-depth information regarding saving and loading, refer to the online documentation at https://ramses-logic.readthedocs.io/en/latest/api.html#save-load-to-file

Attention! This method clears all previous errors! See also docs of getErrors()

Return

true if deserialization was successful, false otherwise. To get more detailed error information use getErrors()

Parameters
  • filename: path to file from which to load content (relative or absolute)

  • ramsesScene: pointer to the Ramses Scene which holds the objects referenced in the Ramses Logic file

  • enableMemoryVerification: flag to enable memory verifier (a flatbuffers feature which checks bounds and ranges). Disable this only if the file comes from a trusted source and performance is paramount.

bool loadFromBuffer(const void *rawBuffer, size_t bufferSize, ramses::Scene *ramsesScene = nullptr, bool enableMemoryVerification = true)

Loads the whole LogicEngine data from the given memory buffer. This method is equivalent to loadFromFile but allows to have the file-opening logic done by the user and only pass the data as a buffer. The logic engine only reads the data, does not take ownership of it and does not modify it. The memory can be freed or modified after the call returns, the LogicEngine keeps no references to it.

Return

true if deserialization was successful, false otherwise. To get more detailed error information use getErrors()

Parameters
  • rawBuffer: pointer to the raw data in memory

  • bufferSize: size of the data (bytes)

  • ramsesScene: pointer to the Ramses Scene which holds the objects referenced in the Ramses Logic file

  • enableMemoryVerification: flag to enable memory verifier (a flatbuffers feature which checks bounds and ranges). Disable this only if the file comes from a trusted source and performance is paramount.

LogicEngine(const LogicEngine &other) = delete

Copy Constructor of LogicEngine is deleted because logic engines hold named resources and are not supposed to be copied

Parameters
  • other: logic engine to copy from

LogicEngine(LogicEngine &&other) = delete

Move Constructor of LogicEngine is deleted because because logic engine is not supposed to be moved

Parameters
  • other: logic engine to move from

LogicEngine &operator=(const LogicEngine &other) = delete

Assignment operator of LogicEngine is deleted because logic engines hold named resources and are not supposed to be copied

Parameters
  • other: logic engine to assign from

LogicEngine &operator=(LogicEngine &&other) = delete

Move assignment operator of LogicEngine is deleted because logic engine is not supposed to be moved

Parameters
  • other: logic engine to move from

Public Members

std::unique_ptr<internal::LogicEngineImpl> m_impl

Implementation detail of LogicEngine

class rlogic::LogicNode
#include <LogicNode.h>

A base class for multiple logic classes which provides a unified interface to their inputs and outputs. Some subclasses don’t have inputs or outputs - in that case the getInputs or getOutputs methods respectively will return nullptr. Some subclasses, like the rlogic::RamsesAppearanceBinding, will have their inputs depending on their current state (in this example the GLSL uniforms of the shader to which the bound ramses Appearance belongs). In those cases, getInputs()/getOutputs() will return a rlogic::Property which represents an empty struct (type Struct, but no child properties).

Subclassed by rlogic::LuaScript, rlogic::RamsesBinding

Public Functions

Property *getInputs()

Returns a property of type Struct which holds the inputs of the LogicNode.

Returns the root Property of the LogicNode which contains potentially nested list of properties. The properties are different for the classes which derive from LogicNode. Look at the documentation of each derived class for more information on the properties. *

Return

a tree like structure with the inputs of the LogicNode

const Property *getInputs() const

Returns a property of type Struct which holds the inputs of the LogicNode.

Returns the root Property of the LogicNode which contains potentially nested list of properties. The properties are different for the classes which derive from LogicNode. Look at the documentation of each derived class for more information on the properties. *

Return

a tree like structure with the inputs of the LogicNode

const Property *getOutputs() const

Returns a property of type Struct which holds the outputs of the LogicNode

Returns the root Property of the LogicNode which contains potentially nested list of properties. The properties are different for the classes which derive from LogicNode. Look at the documentation of each derived class for more information on the properties. *

Return

a tree like structure with the outputs of the LogicNode

std::string_view getName() const

Returns the name of this LogicNode.

Return

the name of the LogicNode

void setName(std::string_view name)

Sets the name of this LogicNode.

Parameters

~LogicNode() noexcept

Destructor of LogicNode

LogicNode(const LogicNode &other) = delete

Copy Constructor of LogicNode is deleted because LogicNodes are not supposed to be copied

Parameters

LogicNode(LogicNode &&other) = delete

Move Constructor of LogicNode is deleted because LogicNodes are not supposed to be moved

Parameters

LogicNode &operator=(const LogicNode &other) = delete

Assignment operator of LogicNode is deleted because LogicNodes are not supposed to be copied

Parameters

LogicNode &operator=(LogicNode &&other) = delete

Move assignment operator of LogicNode is deleted because LogicNodes are not supposed to be moved

Parameters

Public Members

std::reference_wrapper<internal::LogicNodeImpl> m_impl

Implementation detail of LuaScript

Protected Functions

LogicNode(std::reference_wrapper<internal::LogicNodeImpl> impl) noexcept

Constructor of LogicNode. User is not supposed to call this - LogcNodes are created by subclasses

Parameters
  • impl: implementation details of the LogicNode

class rlogic::LuaScript : public rlogic::LogicNode
#include <LuaScript.h>

The LuaScript class is the cornerstone of RAMSES Logic as it encapsulates a Lua script and the associated with it Lua environment. LuaScript instances are created by the rlogic::LogicEngine class.

A LuaScript can be created from Lua source code which must fulfill following requirements:

  • valid Lua 5.1 syntax

  • contains two global functions - interface() and run() with no parameters and no return values

  • declares its inputs and outputs in the interface() function, and its logic in the run() function

  • the interface() function declares zero or more inputs and outputs to the IN and OUT global symbols

  • inputs and outputs are declared like this:

    function interface()
        IN.input_name = TYPE
        OUT.output_name = TYPE
    end
    

  • TYPE is one of [INT|FLOAT|BOOL|STRING|VEC2F|VEC3F|VEC4F|VEC2I|VEC3I|VEC4I], or…

  • TYPE can be also a Lua table with nested properties, obeying the same rules as above, or…

  • TYPE can be an array declaration of the form ARRAY(n, T) where:

    • n is a positive integer

    • T obeys the same rules as TYPE, except T can not be an ARRAY itself

    • T can be a struct, i.e. arrays of structs are supported

  • Each property must have a name (string) - other types like number, bool etc. are not supported as keys

  • the run() function only accesses the IN and OUT global symbols and the properties defined by it

Violating any of these requirements will result in errors, which can be obtained by calling rlogic::LogicEngine::getErrors(). The LuaScript object encapsulates a Lua environment (see official Lua docs) which strips all global table entries after the script is loaded to the Lua state, and leaves only the run() function.

See also the full documentation at https://ramses-logic.readthedocs.io/en/latest/api.html for more details on Lua and its interaction with C++.

Public Functions

std::string_view getFilename() const

Returns the filename provided when the script was created

Return

the filename of the script

void overrideLuaPrint(LuaPrintFunction luaPrintFunction)

Overrides the lua print function with a custom function. Each time “print” is used inside a lua script, the function will be called. Because the lua “print” function allows an arbitrary amount of parameters, the function is called for each provided parameter.

Parameters
  • luaPrintFunction: to use for printing

LuaScript(std::unique_ptr<internal::LuaScriptImpl> impl) noexcept

Constructor of LuaScript. User is not supposed to call this - script are created by other factory classes

Parameters
  • impl: implementation details of the script

~LuaScript() noexcept override

Destructor of LuaScript

LuaScript(const LuaScript &other) = delete

Copy Constructor of LuaScript is deleted because scripts are not supposed to be copied

Parameters
  • other: script to copy from

LuaScript(LuaScript &&other) = delete

Move Constructor of LuaScript is deleted because scripts are not supposed to be moved

Parameters
  • other: script to move from

LuaScript &operator=(const LuaScript &other) = delete

Assignment operator of LuaScript is deleted because scripts are not supposed to be copied

Parameters
  • other: script to assign from

LuaScript &operator=(LuaScript &&other) = delete

Move assignment operator of LuaScript is deleted because scripts are not supposed to be moved

Parameters
  • other: script to move from

Public Members

std::unique_ptr<internal::LuaScriptImpl> m_script

Implementation detail of LuaScript

class rlogic::Property
#include <Property.h>

Represents a generic property slot of the rlogic::LogicNode and its derived classes. Properties can have primitive types (string, integer, etc.) or complex types (structs, arrays). Complex types can have “children”, i.e. nested properties: named fields (structs), or indexed fields (arrays).

Public Functions

EPropertyType getType() const

Returns the type of this Property

Return

the type of this Property

std::string_view getName() const

Returns the name of this Property. Note that not all properties have a name - for example an array element does not have a name. In that case the result will be an empty string view. Struct fields always have a non-empty name.

Return

the name of this Property

size_t getChildCount() const

Returns the amount of available child (nested) properties. If the Property is of type rlogic::EPropertyType::Struct, the returned number will correspond to the number of named properties of the struct. If the Property is of type rlogic::EPropertyType::Array, the method will return the array size. For all other property types getChildCount returns zero.

Return

the number of nested properties.

const Property *getChild(size_t index) const

Returns the child property with the given index. index must be < getChildCount().

This method can be used to get nested properties of structs and arrays. For primitive types this will always return nullptr.

Note that array indexing in getChild follows C++ conventions and not Lua conventions! Inside the Lua scripts, you can and must use Lua conventions when indexing arrays (start at 1, end at N) while in C++ you must use [0, N-1].

Struct properties can also be retrieved by index. The ordering is not guaranteed to match the order of declaration inside Lua scripts (Lua explicitly warns to not rely on ordering of named table entries!). However, once a script is created, the index will not change, i.e. it is permitted that user code caches the property index for faster future access.

Return

the child with the given index, or nullptr if the property is primitive or the index is out of range

Property *getChild(size_t index)

Returns the child property with the given index. index must be < getChildCount().

This method can be used to get nested properties of structs and arrays. For primitive types this will always return nullptr.

Note that array indexing in getChild follows C++ conventions and not Lua conventions! Inside the Lua scripts, you can and must use Lua conventions when indexing arrays (start at 1, end at N) while in C++ you must use [0, N-1].

Struct properties can also be retrieved by index. The ordering is not guaranteed to match the order of declaration inside Lua scripts (Lua explicitly warns to not rely on ordering of named table entries!). However, once a script is created, the index will not change, i.e. it is permitted that user code caches the property index for faster future access.

Return

the child with the given index, or nullptr if the property is primitive or the index is out of range

Property *getChild(std::string_view name)

Searches for a child with the given name. Only properties of type rlogic::EPropertyType::Struct can return a child by name. In case of a primitive property or array this method will return nullptr.

Note that this method may be slower than getChild(size_t index) as it must do a string-based search.

Return

the child with the given name, or nullptr if property is not of type rlogic::EPropertyType::Struct

const Property *getChild(std::string_view name) const

Searches for a child with the given name. Only properties of type rlogic::EPropertyType::Struct can return a child by name. In case of a primitive property or array this method will return nullptr.

Note that this method may be slower than getChild(size_t index) as it must do a string-based search.

Return

the child with the given name, or nullptr if property is not of type rlogic::EPropertyType::Struct

template<typename T>
std::optional<T> get() const

Returns the value of this property. The supported template types are defined by rlogic::IsPrimitiveProperty where IsPrimitiveProperty<T>::value == true for a type T.

Attention! We recommend always specifying the template argument T explicitly, and don’t rely on the compiler’s type deduction! If T is not one of the supported types, a static_assert will be triggered!

Returns nullopt if the template type T does not match the internal type of the property.

Return

the value of this Property as std::optional or std::nullopt if T does not match.

template<typename T>
bool set(T value)

Sets the value of this Property. Same rules apply to template parameter T as in get()

Attention! We recommend always specifying the template argument T explicitly, and don’t rely on the compiler’s type deduction! If T is not one of the supported types, a static_assert will be triggered!

Return

true if setting the value was successful, false otherwise.

Parameters
  • value: the value to set for this Property

Property(std::unique_ptr<internal::PropertyImpl> impl) noexcept

Constructor of Property. User is not supposed to call this - properties are created by other factory classes

Parameters
  • impl: implementation details of the property

~Property() noexcept

Destructor of Property. User is not supposed to call this - properties are destroyed by other factory classes

Property(const Property &other) = delete

Copy Constructor of Property is deleted because properties are not supposed to be copied

Parameters
  • other: property to copy from

Property(Property &&other) = delete

Move Constructor of Property is deleted because properties are not supposed to be moved

Parameters
  • other: property to move from

Property &operator=(const Property &other) = delete

Assignment operator of Property is deleted because properties are not supposed to be copied

Parameters
  • other: property to assign from

Property &operator=(Property &&other) = delete

Move assignment operator of Property is deleted because properties are not supposed to be moved

Parameters
  • other: property to move from

Public Members

std::unique_ptr<internal::PropertyImpl> m_impl

Implementation details of the Property class

Private Functions

template<typename T>
std::optional<T> getInternal() const

Internal implementation of get

template<typename T>
bool setInternal(T value)

Internal implementation of set

template<typename T>
struct PropertyTypeToEnum
#include <EPropertyType.h>

Type trait which converts C++ types to rlogic::EPropertyType enum for primitive types.

template<>
struct rlogic::PropertyTypeToEnum<bool>
#include <EPropertyType.h>

Public Static Attributes

const EPropertyType TYPE = EPropertyType::Bool
template<>
struct rlogic::PropertyTypeToEnum<float>
#include <EPropertyType.h>

Public Static Attributes

const EPropertyType TYPE = EPropertyType::Float
template<>
struct rlogic::PropertyTypeToEnum<int>
#include <EPropertyType.h>

Public Static Attributes

const EPropertyType TYPE = EPropertyType::Int32
template<>
struct rlogic::PropertyTypeToEnum<std::string>
#include <EPropertyType.h>

Public Static Attributes

const EPropertyType TYPE = EPropertyType::String
template<>
struct rlogic::PropertyTypeToEnum<vec2f>
#include <EPropertyType.h>

Public Static Attributes

const EPropertyType TYPE = EPropertyType::Vec2f
template<>
struct rlogic::PropertyTypeToEnum<vec2i>
#include <EPropertyType.h>

Public Static Attributes

const EPropertyType TYPE = EPropertyType::Vec2i
template<>
struct rlogic::PropertyTypeToEnum<vec3f>
#include <EPropertyType.h>

Public Static Attributes

const EPropertyType TYPE = EPropertyType::Vec3f
template<>
struct rlogic::PropertyTypeToEnum<vec3i>
#include <EPropertyType.h>

Public Static Attributes

const EPropertyType TYPE = EPropertyType::Vec3i
template<>
struct rlogic::PropertyTypeToEnum<vec4f>
#include <EPropertyType.h>

Public Static Attributes

const EPropertyType TYPE = EPropertyType::Vec4f
template<>
struct rlogic::PropertyTypeToEnum<vec4i>
#include <EPropertyType.h>

Public Static Attributes

const EPropertyType TYPE = EPropertyType::Vec4i
class rlogic::RamsesAppearanceBinding : public rlogic::RamsesBinding
#include <RamsesAppearanceBinding.h>

The RamsesAppearanceBinding is a type of rlogic::RamsesBinding which allows the rlogic::LogicEngine to control instances of ramses::Appearance. RamsesAppearanceBinding’s can be created with rlogic::LogicEngine::createRamsesAppearanceBinding.

Since the RamsesAppearanceBinding derives from rlogic::RamsesBinding, it also provides the rlogic::LogicNode::getInputs and rlogic::LogicNode::getOutputs method. For this particular implementation, the methods behave as follows:

All shader uniforms are supported, except the following:

  • texture samplers of any kind

  • matrix types (e.g. mat4, mat23 etc.)

  • any uniform with attached semantics (e.g. display resolution) - see ramses::EEffectUniformSemantic docs

Uniform types which are not supported are not available when queried over rlogic::LogicNode::getInputs.

Warning: any references to rlogic::Property objects are invalidated after a call to setRamsesAppearance, even if the newly assigned ramses::Appearance has properties with the same name and type!

Public Functions

void setRamsesAppearance(ramses::Appearance *appearance)

Links this RamsesAppearanceBinding with a ramses::Appearance. After this call, rlogic::LogicNode::getInputs will return a struct property with children equivalent to the uniform inputs of the provided ramses Appearance. Setting the Ramses appearance to nullptr will erase all inputs, and further calls with different Appearances will overwrite the inputs according to the new appearance. Bear in mind that after a call to setRamsesAppearance, pointers to properties of this RamsesAppearanceBinding from before the call will be invalid and must be re-queried, even if some or all of the new appearance’s properties have the same name or type!

Parameters

ramses::Appearance *getRamsesAppearance() const

Returns the currently assigned Ramses Appearance (or nullptr if none was assigned).

Return

the currently bound ramses appearance

RamsesAppearanceBinding(std::unique_ptr<internal::RamsesAppearanceBindingImpl> impl) noexcept

Constructor of RamsesAppearanceBinding. User is not supposed to call this - RamsesAppearanceBinding are created by other factory classes

Parameters

~RamsesAppearanceBinding() noexcept override

Destructor of RamsesAppearanceBinding.

RamsesAppearanceBinding(const RamsesAppearanceBinding &other) = delete

Copy Constructor of RamsesAppearanceBinding is deleted because RamsesAppearanceBinding are not supposed to be copied

Parameters
  • other: RamsesNodeBindings to copy from

RamsesAppearanceBinding(RamsesAppearanceBinding &&other) = delete

Move Constructor of RamsesAppearanceBinding is deleted because RamsesAppearanceBinding are not supposed to be moved

Parameters

RamsesAppearanceBinding &operator=(const RamsesAppearanceBinding &other) = delete

Assignment operator of RamsesAppearanceBinding is deleted because RamsesAppearanceBinding are not supposed to be copied

Parameters

RamsesAppearanceBinding &operator=(RamsesAppearanceBinding &&other) = delete

Move assignment operator of RamsesAppearanceBinding is deleted because RamsesAppearanceBinding are not supposed to be moved

Parameters

Public Members

std::unique_ptr<internal::RamsesAppearanceBindingImpl> m_appearanceBinding

Implementation detail of RamsesAppearanceBinding

class rlogic::RamsesBinding : public rlogic::LogicNode
#include <RamsesBinding.h>

The RamsesBinding is a shared base class for bindings to Ramses objects. For details on each type of binding, look at the derived classes.

Subclassed by rlogic::RamsesAppearanceBinding, rlogic::RamsesNodeBinding

Public Functions

RamsesBinding(std::reference_wrapper<internal::RamsesBindingImpl> impl) noexcept

Constructor of RamsesBinding. User is not supposed to call this - RamsesBinding are created by other factory classes

Parameters

~RamsesBinding() noexcept override = default

Destructor of RamsesBinding.

RamsesBinding(const RamsesBinding &other) = delete

Copy Constructor of RamsesBinding is deleted because RamsesBindings are not supposed to be copied

Parameters
  • other: RamsesBindings to copy from

RamsesBinding(RamsesBinding &&other) = delete

Move Constructor of RamsesBinding is deleted because RamsesBindings are not supposed to be moved

Parameters
  • other: RamsesBindings to move from

RamsesBinding &operator=(const RamsesBinding &other) = delete

Assignment operator of RamsesBinding is deleted because RamsesBindings are not supposed to be copied

Parameters
  • other: RamsesBindings to assign from

RamsesBinding &operator=(RamsesBinding &&other) = delete

Move assignment operator of RamsesBinding is deleted because RamsesBindings are not supposed to be moved

Parameters
  • other: RamsesBindings to assign from

struct rlogic::RamsesLogicVersion
#include <RamsesLogicVersion.h>

Ramses Logic version information.

Public Members

const std::string_view string

Version information as string in format major.minor.patch with an optional arbitrary suffix.

uint32_t major

Major version.

uint32_t minor

Minor version.

uint32_t patch

Patch version.

class rlogic::RamsesNodeBinding : public rlogic::RamsesBinding
#include <RamsesNodeBinding.h>

The RamsesNodeBinding is a type of rlogic::RamsesBinding which allows manipulation of Ramses nodes. RamsesNodeBinding’s can be created with rlogic::LogicEngine::createRamsesNodeBinding.

The RamsesNodeBinding has a fixed set of inputs which correspond to properties of ramses::Node. They have a fixed type, name, and initial value:

  • ‘visibility’ (type bool) [initialized to: true]

  • ’rotation’ (type vec3f) [initialized to: (0, 0, 0)]

  • ’translation’ (type vec3f) [initialized to: (0, 0, 0)]

  • ’scaling’ (type vec3f) [initialized to: (1, 1, 1)]

Warning

The ‘rotation’ property is using the right-handed XYZ Euler convention, which corresponds to the ramses::ERotationConvention::XYZ enum. It is imperative to use the same convention when setting/ getting the rotation values of the bound ramses::Node to avoid unexpected transformation issues. We will try to make this more explicit and error-proof in future releases!

The RamsesNodeBinding class has no output properties (thus getOutputs() will return nullptr) because the outputs are implicitly the properties of the bound Ramses node.

The RamsesNodeBinding is updated every time when rlogic::LogicEngine::update() is executed. The RamsesNodeBinding propagates the values of its inputs to the Ramses node properties accordingly.

Warning

In case no values were set (because the user neither set a value explicitly nor linked the input of rlogic::RamsesNodeBinding to another LogicNode output) the Ramses values are not touched. This is because the default values in Ramses are different than the default property values in the Logic Engine. For example, Ramses nodes have scaling as {1, 1, 1} by default, while Ramses Logic initializes vec3f properties to {0, 0, 0}. It is possible to set values directly to ramses::Node which will not be overwritten by rlogic::RamsesNodeBinding if you never explicitly assigned a value to the rlogic::RamsesNodeBinding inputs. You can also mix-and-match this behavior - assign some properties and others not.

The LogicEngine does not restrict which Scene the bound nodes belong to - it is possible to have nodes from different scenes bound to the same LogicEngine, and vice-versa. The effects on the ramses::Node property values which are bound to a rlogic::RamsesNodeBinding are immediately visible after rlogic::LogicEngine::update() returns, however the user has to call ramses::Scene::flush() explicitly based on their scene update logic and frame lifecycle.

Public Functions

bool setRamsesNode(ramses::Node *node)

Sets the target Ramses node which is bound to this RamsesNodeBinding. Use nullptr as argument to unbind. The Ramses Node is not modified until the next call to rlogic::LogicEngine::update(). After a Ramses Node is unbound, its properties are no longer overwritten by rlogic::RamsesNodeBinding, but their value is also not restored to what it was before it was bound to - it keeps its current state.

Return

true if successful, false otherwise

Parameters
  • node: the Ramses node to bind

ramses::Node *getRamsesNode() const

Returns the currently bound Ramses node, or nullptr if none is bound.

Return

the currently bound Ramses node

bool setRotationConvention(ramses::ERotationConvention rotationConvention)

Sets the rotation convention used to set the rotation values to a potentially bound ramses::Node. Default is the same as the ramses default. Use this to change the setting.

Return

true if successful, false otherwise

Parameters
  • rotationConvention: the rotation convention to use

ramses::ERotationConvention getRotationConvention() const

Returns the currently used rotation convention for the node rotation property.

Return

the currently used rotation convention

RamsesNodeBinding(std::unique_ptr<internal::RamsesNodeBindingImpl> impl) noexcept

Constructor of RamsesNodeBinding. User is not supposed to call this - RamsesNodeBindings are created by other factory classes

Parameters

~RamsesNodeBinding() noexcept override

Destructor of RamsesNodeBinding.

RamsesNodeBinding(const RamsesNodeBinding &other) = delete

Copy Constructor of RamsesNodeBinding is deleted because RamsesNodeBindings are not supposed to be copied

Parameters
  • other: RamsesNodeBindings to copy from

RamsesNodeBinding(RamsesNodeBinding &&other) = delete

Move Constructor of RamsesNodeBinding is deleted because RamsesNodeBindings are not supposed to be moved

Parameters
  • other: RamsesNodeBindings to move from

RamsesNodeBinding &operator=(const RamsesNodeBinding &other) = delete

Assignment operator of RamsesNodeBinding is deleted because RamsesNodeBindings are not supposed to be copied

Parameters
  • other: RamsesNodeBindings to assign from

RamsesNodeBinding &operator=(RamsesNodeBinding &&other) = delete

Move assignment operator of RamsesNodeBinding is deleted because RamsesNodeBindings are not supposed to be moved

Parameters
  • other: RamsesNodeBindings to assign from

Public Members

std::unique_ptr<internal::RamsesNodeBindingImpl> m_nodeBinding

Implementation detail of RamsesNodeBinding

namespace ramses
namespace rlogic

Typedefs

using vec2f = std::array<float, 2>
using vec3f = std::array<float, 3>
using vec4f = std::array<float, 4>
using vec2i = std::array<int, 2>
using vec3i = std::array<int, 3>
using vec4i = std::array<int, 4>
using LuaPrintFunction = std::function<void(std::string_view scriptName, std::string_view message)>

Enums

enum ELogMessageType

ELogMessageType lists the types of available log messages. The integer represents the priority of the log messages (lower means more important, thus higher priority).

Values:

enumerator ERROR = 2
enumerator WARNING = 3
enumerator INFO = 4
enumerator DEBUG = 5
enum EPropertyType

EPropertyType lists the types of properties created and managed by the rlogic::LogicNode class and its derivates.

Values:

enumerator Float
enumerator Vec2f
enumerator Vec3f
enumerator Vec4f
enumerator Int32
enumerator Vec2i
enumerator Vec3i
enumerator Vec4i
enumerator Struct
enumerator String
enumerator Bool
enumerator Array

Functions

constexpr const char *GetLuaPrimitiveTypeName(EPropertyType type)

Returns the string representation of a property type. This string corresponds to the syntax that has to be used in the Lua source code used to create scripts with properties with the corresponding type.

RamsesLogicVersion GetRamsesLogicVersion()

Retrieve currently used Ramses Logic version information.

Return

the Ramses Logic version of the currently used build

namespace internal
namespace rlogic::Logger

Interface to interact with the internal logger. If you want to handle log messages by yourself, you can register your own log handler function with rlogic::Logger::SetLogHandler, which is called each time a log message is logged. In addition you can silence the standard output of the log messages

Typedefs

using LogHandlerFunc = std::function<void(ELogMessageType, std::string_view)>

The LogHandlerFunc can be used to implement a custom log handler. The function is called for each log message separately. After the call to the function the string data behind std::string_view is deleted. If you want to keep it, you must copy it e.g. to a std::string. E.g.

rlogic::Logger::SetLogHandler([](ElogMessageType msgType, std::string_view message){
    std::cout << message std::endl;
});

Functions

void SetLogVerbosityLimit(ELogMessageType verbosityLimit)

Controls how verbose the logging is. verbosityLimit has the following semantics:

  • if log message has message type with higher or equal priority as verbosityLimit, then it is logged

  • log priority is as documented by rlogic::ELogMessageType (Errors are more important than Warnings, etc)

  • the default value is rlogic::ELogMessageType::INFO, meaning that log messages are processed if they have INFO priority or higher.

Parameters
  • verbosityLimit: least priority a log message must have in order to be processed

ELogMessageType GetLogVerbosityLimit()

Returns the current log verbosity limit of the logger. See rlogic::Logger::SetLogVerbosityLimit for more info on semantics.

Return

current log verbosity limit

void SetLogHandler(const LogHandlerFunc &logHandlerFunc)

Sets a custom log handler function, which is called each time a log message occurs. Note: setting a custom logger incurs a slight performance cost because log messages will be assembled and reported, even if default logging is disabled (SetDefaultLogging).

@ param logHandlerFunc function which is called for each log message

void SetDefaultLogging(bool loggingEnabled)

Sets the default logging to std::out to enabled or disabled. Enabled by default.

Parameters
  • loggingEnabled: true if you want to enable logging to std::out, false otherwise

file APIExport.h

Defines

RLOGIC_API
file Collection.h
#include “ramses-logic/APIExport.h”#include “ramses-logic/Iterator.h”#include <functional>#include <memory>
file ELogMessageType.h
#include <cassert>
file EPropertyType.h
#include “ramses-logic/APIExport.h”#include <array>#include <string>
file Iterator.h
#include “ramses-logic/APIExport.h”#include <type_traits>#include <iterator>
file Logger.h
#include “ramses-logic/APIExport.h”#include “ramses-logic/ELogMessageType.h”#include <functional>#include <string_view>
file LogicEngine.h
#include “ramses-logic/APIExport.h”#include “ramses-logic/LuaScript.h”#include “ramses-logic/Collection.h”#include <vector>#include <string>#include <string_view>
file LogicNode.h
#include “ramses-logic/APIExport.h”#include <vector>#include <string>#include <functional>
file LuaScript.h
#include “ramses-logic/LogicNode.h”#include <string>#include <memory>
file Property.h
#include “ramses-logic/APIExport.h”#include “ramses-logic/EPropertyType.h”#include <cstddef>#include <optional>#include <memory>#include <string>
file RamsesAppearanceBinding.h
#include “ramses-logic/APIExport.h”#include “ramses-logic/RamsesBinding.h”#include <memory>
file RamsesBinding.h
#include “ramses-logic/LogicNode.h”
file RamsesLogicVersion.h
#include “ramses-logic/APIExport.h”#include <string_view>#include <cstdint>
file RamsesNodeBinding.h
#include “ramses-logic/APIExport.h”#include “ramses-logic/RamsesBinding.h”#include <memory>
dir /home/docs/checkouts/readthedocs.org/user_builds/ramses-logic/checkouts/v0.5.3/include
dir /home/docs/checkouts/readthedocs.org/user_builds/ramses-logic/checkouts/v0.5.3/include/ramses-logic