Jump to content

Editor workflow discussion


Josh
 Share

Recommended Posts

This will give you a better idea of how editor scripting works:
https://www.ultraengine.com/learn/EditorEvents?lang=lua

The editor emits a lot of extra events to trigger actions. You can intercept these events and override them or add your own functionality.

For example, you could intercept the EVENT_OPENASSET event and make a script file open in a new script editor interface.

  • Like 4

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

This Lua code will work right now in the current build of 1.0.2:

--Get the displays
local displays = GetDisplays()

--Create a window
local window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[1], WINDOW_CENTER | WINDOW_TITLEBAR)

--Create a framebuffer
local framebuffer = CreateFramebuffer(window)

--Create a world
local world = CreateWorld()

--Create a camera
local camera = CreateCamera(world, 2)
camera:SetClearColor(0,0,1,1)
camera:SetPosition(0,0,-2)

--Create a model
local box = CreateBox(world)

--Create a light
--local light = CreateBoxLight(world)
--light:SetRotation(45,35,0)

while window:KeyDown(KEY_ESCAPE) == false and window:Closed() == false do

	--Rotate the model
	box:Turn(0,0.1,0)

	--Update the world
	world:Update()

	--Render the world to the framebuffer
	world:Render(framebuffer)

end

And this shows how the C++ side is set up:

https://github.com/UltraEngine/Documentation/blob/master/CPP/PollDebugger.md

image.thumb.png.31d6c16123daeb261cde00f88abb29dc.png

  • Like 1

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

This is all so very cool and exciting..   How far along is the entire engine accessible via LUA?

It looks like to me that porting a LE LUA project over to Ultra LUA is as simple as copy n paste.  :)_

I'm super pumped in seeing this part of the engine developed further, the debugging via vscode using lua is out of this world. Can someone say 'Noble pr .....' hehe :) 

Link to comment
Share on other sites

For entity properties, I plan to use JSON files that define the properties that appear in the editor, rather than parsing the script or code files. This will provide more control and less confusing about how properties can be described, and it can be standard for Lua / C++ / C# and even no-code (third-party games).

Something like this:

{
	"componentTemplate":
	{
      	"name": "player",
        "label": "Player",
        "language": "c++",
      	"properties":
        [
          {
            "name": "health",
            "label": "Health",
            "type": "INTEGER",
            "minimum": 1,
            "maximum": 100,
            "default": 75
          },
          {
            "name": "target",
            "label": "Target",
            "type": "ENTITY",
            "default": null
          }
    	]
	}
}

These configuration files can either be stored in the game folder, or in a folder of config settings that get loaded based on a command line switch. For example, a configuration for the game Quake can be stored and loaded with a lunch parameter:

Editor.exe +game Quake

We will have a standard way of defining available properties and getting data into Ultra Engine, for use with any supported language.

We also now have a way of exposing C++ classes to Lua with official documentation, and I have worked out all the gotchas of the binding code and documented the information. It's not super easy to set up, but it's also not super hard, and the results it provides are very very good.

This has led me to think about the current design of C++ components. Here are the things I like:

  • Standardizing load, save, copy, and updating is very good.
  • The syntax shared_ptr<T> AddComponent<T>() is very good.
  • Making C++ components header-only, while not required, provides a very convenient way to include bits of functionality.

Here are the things I don't like:

  • C++ components work slightly differently from Lua scripts attached to entities, and it will also be different from the C# implementation.
  • The Actor / Entity dichotomy is strange but necessary for the design to work.
  • Parsing header files is never going to be perfect.
  • The behavior of C++ components is interesting but extremely nonstandard.
  • The preprocessor works reliably, but it is a piece of technical debt that will undoubtedly require additional work in the future.

I'm thinking it may be best to dial things back a bit, keep the good aspects of this, but try to make something that syncs up better with how people will expect their games to work. The way both Lua and C++ in Ultra call a function on each component when you make a single function call is very clever and innovative, but I am not sure the benefit is great enough to justify breaking peoples' expectations and introducing ambiguity. Here is what I am picturing:

  • C++ components are attached directly to an entity with an Entity::AddComponent<T>() method. No "Actor" class.
  • Methods can only be explicitly called on the component you want to call it on.
  • Load and save methods are manually filled in for each component. The engine will call the methods, but the end user needs to fill in the values.
  • Start(), Update(), Collision(), Load(), and Save() still get called by the engine at the appropriate times, for each component.
  • C++, C#, and Lua components all work the same way.

C++ example:

#include "Components/HealthManager.hpp"

auto component = entity->AddComponent<HealthManager>();
component->health = 75;

Lua example:

local component = entity:AddComponent("Scripts/Components/HealthManager.lua")
component.health = 75

C# example (???):

#include "Components/HealthManager.cs"

var component = entity.AddComponent<HealthManager>();
component.health = 75;

What do you think?

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

I do agree that the actor class is redundant. If I wanted to identify an entity as a puzzle box, I can just set it's tag to "puzzlebox" and assign the needed components to it. This tag can be used for filtering as I would not want another physics object (like a soda can or a crate) to activate my puzzle button. The idea of the actor was to have a body for the components, but the entity class can already do that.

An idea I have is to use the header files to automatically generate the entity property json file. Point application to where your source code is and it should all magically work. Nobody likes making configuration files, and it should remove a lot of friction.

I also think a similar application should be used to generate sol bindings automatically, but it's not that necessary.

Also interested in how I/O should work. Ultra needs to do better than the existing flow graph in Leadwerks.

Cyclone - Ultra Game System - Component PreprocessorTex2TGA - Darkness Awaits Template (Leadwerks)

If you like my work, consider supporting me on Patreon!

Link to comment
Share on other sites

15 minutes ago, reepblue said:

The idea of the actor was to have a body for the components, but the entity class can already do that.

It wasn't my preference but it was absolutely necessary for the type of "method collection" the current system does. The Actor has methods defined by the components that are attached to it. Calling the Actor method calls the same method on each component that has it. Since these methods don't get defined until your game is compiled, it's not possible to attach them directly to the entity class in the engine.

I don't really like component systems anyways, so I'm fine just doing it the way people expect it to work coming from unity. I'm happy with a single object attached to each entity with an OOP hierarchy, which is also doable with this.

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

51 minutes ago, Josh said:

I'm happy with a single object attached to each entity with an OOP hierarchy, which is also doable with this.

I'm also happy with this and I plan for most of my work to be done this way. I find entity component systems to get confusing, but I understand why people like them.

Cyclone - Ultra Game System - Component PreprocessorTex2TGA - Darkness Awaits Template (Leadwerks)

If you like my work, consider supporting me on Patreon!

Link to comment
Share on other sites

In fact, I think the game configuration can include a setting for "Entity Component System" or "Object-Oriented". The actual difference in the editor would just be selecting one class vs. adding multiple components, but it's a different way of thinking.

Here's an example of the new proposed approach that will work right now in 1.0.2. I named the base component class Component_ to avoid interfering with the existing system. The only methods that will get called right now are Start() and Update().

#include "UltraEngine.h"

using namespace UltraEngine;

class CameraControls_ : public Component_
{
public:
    bool freelookstarted = false;
    float mousesmoothing = 0.0f;
    float mouselookspeed = 1.0f;
    float movespeed = 4.0f;
    Vec3 freelookmousepos;
    Vec3 freelookrotation;
    Vec2 lookchange;

    virtual void Update()
    {
        auto entity = GetEntity();

        auto window = ActiveWindow();
        if (window == NULL) return;
        if (!freelookstarted)
        {
            freelookstarted = true;
            freelookrotation = entity->GetRotation(true);
            freelookmousepos = window->GetMouseAxis();
        }
        auto newmousepos = window->GetMouseAxis();
        lookchange.x = lookchange.x * mousesmoothing + (newmousepos.y - freelookmousepos.y) * 100.0f * mouselookspeed * (1.0f - mousesmoothing);
        lookchange.y = lookchange.y * mousesmoothing + (newmousepos.x - freelookmousepos.x) * 100.0f * mouselookspeed * (1.0f - mousesmoothing);
        if (Abs(lookchange.x) < 0.001f) lookchange.x = 0.0f;
        if (Abs(lookchange.y) < 0.001f) lookchange.y = 0.0f;
        if (lookchange.x != 0.0f or lookchange.y != 0.0f)
        {
            freelookrotation.x += lookchange.x;
            freelookrotation.y += lookchange.y;
            entity->SetRotation(freelookrotation, true);
        }
        freelookmousepos = newmousepos;
        float speed = movespeed / 60.0f;
        if (window->KeyDown(KEY_SHIFT))
        {
            speed *= 10.0f;
        }
        else if (window->KeyDown(KEY_CONTROL))
        {
            speed *= 0.25f;
        }
        if (window->KeyDown(KEY_E)) entity->Translate(0, speed, 0);
        if (window->KeyDown(KEY_Q)) entity->Translate(0, -speed, 0);
        if (window->KeyDown(KEY_D)) entity->Move(speed, 0, 0);
        if (window->KeyDown(KEY_A)) entity->Move(-speed, 0, 0);
        if (window->KeyDown(KEY_W)) entity->Move(0, 0, speed);
        if (window->KeyDown(KEY_S)) entity->Move(0, 0, -speed);
    }
};

int main(int argc, const char* argv[])
{
    //Get the displays
    auto displays = GetDisplays();

    //Create a window
    auto window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR);

    //Create a world
    auto world = CreateWorld();
    world->SetAmbientLight(0);

    //Create a framebuffer
    auto framebuffer = CreateFramebuffer(window);

    //Load FreeImage plugin
    auto plg = LoadPlugin("Plugins/FITextureLoader");

    //Load model
    //Cyber Samurai by Khoa Minh: https://sketchfab.com/3d-models/cyber-samurai-26ccafaddb2745ceb56ae5cfc65bfed5
    auto model = LoadModel(world, "https://github.com/UltraEngine/Documentation/raw/master/Assets/Models/Characters/cyber_samurai.glb");
    model->Turn(0, 180, 0, true);

    //Environment maps
    auto specmap = LoadTexture("https://github.com/UltraEngine/Assets/raw/main/Materials/Environment/footprint_court/specular.dds");
    auto diffmap = LoadTexture("https://github.com/UltraEngine/Assets/raw/main/Materials/Environment/footprint_court/diffuse.dds");
    world->SetEnvironmentMap(diffmap, ENVIRONMENTMAP_BACKGROUND);
    world->SetEnvironmentMap(specmap, ENVIRONMENTMAP_SPECULAR);
    world->SetEnvironmentMap(diffmap, ENVIRONMENTMAP_DIFFUSE);

    //Create a camera    
    auto camera = CreateCamera(world);
    camera->SetClearColor(0.125);
    camera->SetPosition(0, 1.4, -1);
    camera->SetFov(70);
    camera->AddPostEffect(LoadPostEffect("Shaders/PostEffects/FXAA.json"));

    //Camera controls
    camera->AddComponent<CameraControls_>();

    //auto actor = CreateActor(camera);
    //actor->AddComponent<CameraControls>();

    //Create light
    auto light = CreateBoxLight(world);
    light->SetRange(-10, 10);
    light->SetArea(15, 15);
    light->SetRotation(45, 35, 0);
    light->SetColor(1.2);

    //Main loop
    while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false)
    {
        world->Update();
        world->Render(framebuffer);
    }
    return 0;
}

 

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Yeah, as long as the editor is smart enough to know hierarchies, it should work. I should be able to derive from CameraControls_ to make MyCameraControls with still getting the same values from the base class.

Cyclone - Ultra Game System - Component PreprocessorTex2TGA - Darkness Awaits Template (Leadwerks)

If you like my work, consider supporting me on Patreon!

Link to comment
Share on other sites

I've made some additions:

Instead of Component_::GetEntity() there is now a raw pointer for the entity. It's can't be a shared pointer or it would create a circular reference and prevent the entity from ever being deleted. I think this will be okay, I was doing the same thing in the preprocessor component system.

Entity::components is now accessible (read-only vector containing all attached components).

Component_::Collide method will now get called by the engine.

Added Component_::Copy(). This will get called when an entity is instantiated or copied, so you can duplicate objects in memory and retain all properties. This will also allow duplication of complex C++ data that can't be stored in a JSON file:

virtual shared_ptr<Component_> Copy()
{
	auto component = std::make_shared<Mover>(*this);// copies all members
	//
	// Make any adjustments here
	//
	return component;
}

My current outlook is that processing data for each component isn't that hard, and with C# and Lua that process can probably be automated using reflection. What is hard is editing and storing the component data in a standard way, so that is what the engine should focus on. Filling in a Component::Save method so your class members get saved into a JSON file isn't too terribly difficult, and the overly complicated alternative I came up with is probably worse. Maybe something like this can be utilized in the future.

Here is a working example that copies an entity with a component:

#include "UltraEngine.h"

using namespace UltraEngine;

class Mover : public Component_
{
public:

    Vec3 movement;
    Vec3 rotation;
    bool globalcoords = false;

    virtual void Update()
    {
        if (globalcoords)
        {
            this->entity->Translate(movement / 60.0f, true);
        }
        else
        {
            this->entity->Move(movement / 60.0f);
        }
        this->entity->Turn(rotation / 60.0f, globalcoords);
    }

    virtual shared_ptr<Component_> Copy()
    {
        return std::make_shared<Mover>(*this);
    }
};

int main(int argc, const char* argv[])
{
    //Get the displays
    auto displays = GetDisplays();

    //Create a window
    auto window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR);

    //Create a world
    auto world = CreateWorld();
    world->SetAmbientLight(0);

    //Create a framebuffer
    auto framebuffer = CreateFramebuffer(window);

    //Load FreeImage plugin
    auto plg = LoadPlugin("Plugins/FITextureLoader");

    //Load model
    //Cyber Samurai by Khoa Minh: https://sketchfab.com/3d-models/cyber-samurai-26ccafaddb2745ceb56ae5cfc65bfed5
    auto model = LoadModel(world, "https://github.com/UltraEngine/Documentation/raw/master/Assets/Models/Characters/cyber_samurai.glb");
    model->Turn(0, 180, 0, true);
    model->SetPosition(1, 0, 0);
  
    //===================================================================
    // Add a component to the entity
    //===================================================================

	auto mover = model->AddComponent<Mover>();
    mover->rotation.z = 10;

    //===================================================================
    // Copy an entity with components (very cool)
    //===================================================================

    auto model2 = model->Instantiate(world);
    model2->SetPosition(-1, 0 , 0);

    //Environment maps
    auto specmap = LoadTexture("https://github.com/UltraEngine/Assets/raw/main/Materials/Environment/footprint_court/specular.dds");
    auto diffmap = LoadTexture("https://github.com/UltraEngine/Assets/raw/main/Materials/Environment/footprint_court/diffuse.dds");
    world->SetEnvironmentMap(diffmap, ENVIRONMENTMAP_BACKGROUND);
    world->SetEnvironmentMap(specmap, ENVIRONMENTMAP_SPECULAR);
    world->SetEnvironmentMap(diffmap, ENVIRONMENTMAP_DIFFUSE);

    //Create a camera    
    auto camera = CreateCamera(world);
    camera->SetClearColor(0.125);
    camera->SetPosition(0, 0.9, -2);
    camera->SetFov(70);
    camera->AddPostEffect(LoadPostEffect("Shaders/PostEffects/FXAA.json"));

    //Create light
    auto light = CreateBoxLight(world);
    light->SetRange(-10, 10);
    light->SetArea(15, 15);
    light->SetRotation(45, 35, 0);
    light->SetColor(1.2);

    //Main loop
    while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false)
    {
        world->Update();
        world->Render(framebuffer);
    }
    return 0;
}

 

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

@klepto2 @gothboiclique An overload of Entity::AddComponent is added for use with other languages:

shared_ptr<Component_> Entity::AddComponent(const ComponentHooks& hooks, void* extra, const bool start)

The ComponentHooks structurs consists of six function pointers. Any value may be NULL or a pointer to a C-style function:

	struct ComponentHooks
	{
		void(*Start)(void*);
		void(*Update)(void*);
		void(*Collide)(void*, void*, const dFloat*, const dFloat*, dFloat);
		void*(*Copy)(void*);
		int(*Load)(void*, char*, uint64_t);
		int(*Save)(void*, char*, uint64_t*);
	};

The extra value is a user-defined pointer that gets passed in the first parameter of each callback.

All but the Load and Save callbacks are working now. This will allow the components system to work with C# and other languages.

Please let me know if I need to improve it in some way.

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Components are working with Lua now in 1.0.2. Only Start and Update will currently be called. Here's an example:

#include "UltraEngine.h"

using namespace UltraEngine;

int main(int argc, const char* argv[])
{
    //Get the displays
    auto displays = GetDisplays();

    //Create window
    auto window = CreateWindow("Ultra Engine", 0, 0, 800, 600, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR);

    //Create world
    auto world = CreateWorld();

    //Create framebuffer
    auto framebuffer = CreateFramebuffer(window);

    //Create a camera
    auto camera = CreateCamera(world);
    camera->SetClearColor(0, 0, 1);
    camera->SetPosition(0, 0, -4);

    auto light = CreateDirectionalLight(world);
    light->SetRotation(45, 25, 0);

    auto box = CreateBox(world);
    box->AddComponent("Scripts/Components/Mover.lua");
    
    while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false)
    {
        world->Update();
        world->Render(framebuffer);
    }
    return 0;
}

 

  • Like 3

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Okay, so with this approach we have one system and C++, Lua, and C# are all accounted for and work in an identical manner. The next step will be to make the flowgraph connections work with the system. For Lua, the firing and inputs can be automated. For C++ you will need to manually call the outputs and have a method that interprets the inputs like this:

void MyComponent::ReceiveSignal(const WString& function)
{
	if (function == "Open")
	{
		this->Open();
	}
	else if (function == "Close")
	{
		this->Close();
	}
}

I don't see any other way to do it without a preprocesser. This approach might be an improvement, but you would still need to put the method pointers into a map.

For C# it is probably possible to automatically trigger the right function, but for now I will rely on other people to tell me what I need to do to best support that.

I think the pattern we are going to see is that the components system is mostly automated with Lua and C#, and with C++ you need to write explicit handling code, for receiving signals, and for Load and Save. I think that's okay given the nature of the language.

The next step would be arguments, but I would like to get this working with all three languages before I proceed with that. I'm thinking something like this, where DynamicValue is a special class that contains an ID for the type and a value for integers, strings, floats, objects, etc., and it automatically converts to each supported type with an = operator:

void MyComponent::ReceiveSignal(const WString& function, std::vector<DynamicValue> arguments)
{
	if (function == "Open")
	{
  		float speed = 1;
  		shared_ptr<Sound> noise;
  		if (arguments.size() > 0 and arguments[0].type == TYPE_NUMBER) speed = arguments[0];
  		if (arguments.size() > 1 and arguments[1].type == TYPE_OBJECT) sound = arguments[1]->As<Sound>();
		this->Open(speed, noise);
	}
}

Lua can handle the above automatically with its very flexible function calling. I'm guessing something automatic can probably be done for C#?

  • Like 1

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

The Lua entity component system will allow you to write scripts two ways. The default is ECS, which will work like unity:

entity.healthmanager.health = 100
entity.player:UpdateControls()

There's also an OOP mode that attaches values and functions directly to the entity class (I wanted to do this in Leadwerks but couldn't at the time):

entity.health = 100
entity:UpdateControls()

I like OOP mode because you can write scripts like this:

function Entity:Update()
	self:Turn(0,1,0)
end

Instead of this:

function Component:Update()
	self.entity:Turn(0,1,0)
end

However, the default mode and everything the documentation uses will assume ECS mode. If you use OOP mode you will break compatibility with all third-party scripts. For the people this option appeals to, that probably isn't a problem.

These will be defined in a "ComponentSystem.lua" file

__ULTRA_COMPONENTSYSTEMMODE = "ECS"-- default behavior
--__ULTRA_COMPONENTSYSTEMMODE = "OOP"-- experimental, will break compatibility with other scripts

--This function is called after a Lua component is attached to an entity
function __ULTRA_FinalizeComponent(entity, component, name, table)

    -- Attach values to the component object
    if __ULTRA_COMPONENTSYSTEMMODE == "ECS" then
        entity[name] = component
        component.entity = entity
    end

    -- Attach values to the entity itself
    local componenthandle = component
    if __ULTRA_COMPONENTSYSTEMMODE == "OOP" then
        component = entity
    end

    local k, v
    for k, v in pairs(table) do
        --Print(k)
        local t = type(v)
        if t == "table" then
            component[k] = table.copy(v)
        elseif t == "function" then
            local function func(c, ...)
                v(c, ...)
                componenthandle:FireOutputs(k)-- Automatically fires outputs for this method whenever it is called
            end
            component[k] = func
        else
            component[k] = v
        end
    end
end

--This function is called whenever the engine wants to execute a predefined component hook
function __ULTRA_CallComponentMethod(entity, component, funcname, ...)

    --Call methods from the entity instead of the component object
    if __ULTRA_COMPONENTSYSTEMMODE == "OOP" then
        component = entity
    end
    
    --Exit silently if the function doesn't exist
    if component[funcname] == nil then return false end

    --Safety check
    if type(component[funcname]) ~= "function" then
        Print("Error: Function type must be function but instead it is "..type(component[funcname])..".")
        return false
    end

    --Execute with component as first argument, followed by rest of arguments
    component[funcname](component, ...)

    return true
end

You could also modify this file to handle components another way if you wanted, but again, ECS will be the default expected behavior.

  • Like 2

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

The Lua component system stuff is now stored in a single table:

ComponentSystem = {}-- global variable

ComponentSystem.mode = "ECS"-- default behavior
--ComponentSystem.mode = "OOP"-- experimental, will break compatibility with ECS-style scripts

--This function is called when a component is attached to an entity
function ComponentSystem:AddComponent(entity, component, name)
	local table = require("Components."..name)
	if table == nil then return false end
  
	blah blah blah...

The script itself is now responsible for the execution of the component script. It uses require() so the component script only gets executed once and the same table's values are copied to the C++ component object each time. Since it uses require() it expects a Lua module name, not a script name, so the usage will look like this:

entity:AddComponent("Mover")

I plan to organize components into subfolders one layer deep, so the final usage will be more like this:

entity:AddComponent("Motion.Mover")

I have no idea how or if this will interact with C#. What will the user syntax to add a C# component look like? Will it use a string like this, or something else?

Do we plan on Lua ever working with a C# game? I'm leaning towards no, because C# users aren't going to be coming from that mindset or have that expectation.

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

I've worked out the basic flowgraph system:
https://www.ultraengine.com/learn/Component_Connect?lang=cpp
https://www.ultraengine.com/learn/Component_FireOutputs?lang=cpp
https://www.ultraengine.com/learn/Component_ReceiveSignal?lang=cpp

I don't know yet exactly how std::any works, or how functions as arguments will work. I doubt I can really work that out until the visual interface is usable, but the basic implementation is done.

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Here's a quick test using std::any:

#include "UltraEngine.h"
#include <any>
  
using namespace UltraEngine;

int main(int argc, const char* argv[])
{
    std::any thing = 3;
    //thing = String("test");
    //thing = 5u;

    if (thing.has_value())
    { 
        auto name = String(thing.type().name());
        if (name == "int")
        {
            Print(std::any_cast<int>(thing));
        }
        else if (name == "float")
        {
            Print(std::any_cast<float>(thing));
        }
        else if (name == "class UltraEngine::String")
        {
            Print(std::any_cast<String>(thing));
        }
        else
        {
            Print("Unknown type \"" + name + "\"");
        }
    }
    else
    {
        Print("thing is empty.");
    }
    return 0;
}

 

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Component connections are added now, with arguments. Here is a working example. A lot of stuff has to be defined manually in C++. In Lua this is all automated, including the output firing:

#include "UltraEngine.h"

using namespace UltraEngine;

struct Sender : public Component
{
    virtual void MyMethod()
    {
        //----------------------------------------------------
        // You probably want to execute some other code here
        //----------------------------------------------------

        Print("Firing output MyMethod");
        FireOutputs("MyMethod");
    }
};

struct Receiver : public Component
{
    virtual void AnotherMethod(const int i)
    {
        Print("Executing method with argument: " + String(i));

        //----------------------------------------------------
        // You probably want to execute some other code here
        //----------------------------------------------------
    }

    virtual void ReceiveSignal(shared_ptr<Component> sender, const WString& input, std::vector<std::any> args)
    {
        Print("Signal received: " + input);

        //Choose the method to call
        if (input == "AnotherMethod")
        {
            //Fix arguments
            args.resize(1);
            if (strcmp(args[0].type().name(), "int") != 0) args[0] = 0;

            //Call the method
            AnotherMethod(std::any_cast<int>(args[0]));
        }
        else
        {
            Print("Unknown method " + input);
        }
    }
};

int main(int argc, const char* argv[])
{
    //Create sending object
    auto box1 = CreateBox(NULL);
    auto sender = box1->AddComponent<Sender>();

    //Create receiving object
    auto box2 = CreateBox(NULL);
    auto receiver = box2->AddComponent<Receiver>();

    //Add connection
    sender->Connect("MyMethod", receiver, "AnotherMethod", { 3 });

    //Trigger the connection
    sender->MyMethod();

    return 0;
}

I think the same thing in Lua will work like this, without any manual processing or firing:

Sender = {}
function Sender:MyMethod()
    Print("Firing output MyMethod");
end

Receiver = {}
function Receiver:AnotherMethod(i)
    Print("Executing method with argument: "..tostring(i))
end

--Create sending object
auto box1 = CreateBox(NULL)
auto sender = box1->AddComponent(Sender)

--Create receiving object
auto box2 = CreateBox(NULL)
auto receiver = box2->AddComponent(Receiver)

--Add connection
sender->Connect(Sender.MyMethod, receiver, Receiver.AnotherMethod, { 3 })

--Trigger the connection
sender->MyMethod()

 

  • Like 1

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

You can now create Lua components from a table as well:

local component = {}

function component:Update()
	self.entity:Turn(10,0,0)
end

box:AddComponent(component, "MyComponent")

You need to go through the AddComponent() command, not just add tables to your entity, because this creates the C++ component and handles a bunch of stuff. 

I did this primarily for ease of showing things in the documentation. If a special component is needed it's easy to just add it into the documentation example.

It looks like std::any will probably work for functions-as-arguments as well:

int FUNC()
{
    return 3;
}

int main(int argc, const char* argv[])
{
    std::any thing = FUNC;
    Print(thing.type().name());

    auto f = std::any_cast<int(__cdecl*)(void)>(thing);
    Print(f());

    return 0;
}

 

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...