Jump to content

Plugins Expanded

Josh

313 views

This month I was working on a lot of NASA projects using Leadwerks. My goal with this is to build up enough business that I can start hiring more people to help. It's nice to make money using my own tools, and it gives me a lot of ideas for the new engine.

This morning I felt like experimenting with the design of plugins in the new editor a bit. I came up with two types of plugins. One will add an new menu item to the model editor that flips the normals of the current model. The ProcessEvent() function intercepts a GUI event in the editor and performs new functionality:

Plugin.title = "Flip Model Normals"
Plugin.description = "Reverses the faces of all triangles in the model editor."

function Plugin:Load()
    local menu = Editor.modelEditor.menu:FindMenu("Plugins")
    if menu~=nil then
        self.menuitem = menu:AddItem("Flip Normals")
    end
end

function Plugin:Unload()
    if self.menuitem~=nil then self.menuitem:Free() end
end

function Plugin:ProcessEvent(event)
    if event.id = EVENT_MENUACTION then
        if event.source = self.menuitem then
            if Editor.modelEditor.model~=nil then
                self:FlipNormals(Editor.modelEditor.model)
                Editor.modelEditor:Redraw()
                return false
            end
        end
    end
    return true
end

function Plugin:FlipNormals(model)
    local n,k,lod,mesh,i,a,b,c
    for n=0,#model.lods do
        lod = model.lods[n]
        for k=0,#lod.meshes do
            mesh = lod.meshes[k]
            mesh:Unlock()
            for i=0,#mesh.indices/3 do
                a = mesh.indices[i*3+0]
                b = mesh.indices[i*3+1]
                c = mesh.indices[i*3+2]
                mesh.indices[i*3+0] = c
                mesh.indices[i*3+2] = a
            end
            mesh:Lock()
        end
    end
end

Another type of plugin adds some new functionality to your game. This one is different because it loads a function from a dynamically linked library and passes the Lua state to a function: In this case, I decided to try separating analytics off into its own plugin, simply because it is a self-contained system that doesn't interfere with the rest of the engine:

Plugin.name = "Example"
Plugin.title = "Example Plugin"
Plugin.description = "This is an example plugin."

function Plugin:GetPath()
    local ext=""
    if GetOS()=="Windows" then
        ext = "dll"
    elseif GetOS()=="Linux" then
        ext = "so"
    elseif GetOS()=="MacOS"
        ext = "dylib"
    end
    return self.name..ext
end

function Plugin:Load()
    local f = package.loadlib(self:GetPath(),"Load")
    if type(f)=="function" then f(GetLuaState()) end
end

function Plugin:Unload()
    local f = package.loadlib(self:GetPath(),"Unload")
    if type(f)=="function" then f() end
end

The source code for the dynamic library would be something like this:

#include "GameAnalytics.h"

bool Load(sol::state* L)
{
	L->set_function("EnableAnalytics", EnableAnalytics);
}

void Unload()
{

}

bool EnableAnalytics(const bool state)
{
	return true;
}

Once the plugin is loaded, the Lua state would be passed to the DLL (or .so, or .dylib) and the new commands would get inserted into the Lua state.

So what does a plugin consist of?

  • A ,lua or precompiled .luac file is required.
  • If the plugin uses C++ code, a .dll, .so, and .dylib are required.
  • Optional source code and project(s) for the dynamically linked library.

And what can a plugin do?

  • Add new features to the editor.
  • Add new Lua commands to use in your game.

That's all for now.

  • Like 4
  • Upvote 2


12 Comments


Recommended Comments

Really cool. This will help with users building the game with their own systems instead of fighting with any conflicting ones.

Just keep the default plugs to a minimum and provide docs and examples. 🙂

Share this comment


Link to comment

I'm thinking about makign VSCode the official C++ IDE as well. We have one IDE that can handle C++ and Lua, on all platforms.

Share this comment


Link to comment

How can you compile projects with just VSCode? I can see if you were using the Windows GCC stuff and used makefiles, but you need the complete VS2017 to build anything using MSVC as far as I know.

Share this comment


Link to comment

@reepblue You can compile with Visual Studio compiler using Makefiles.

Have a look at CMake.

Share this comment


Link to comment

So I have a question about the LUA and DLL stuff. Does that mean I could search for things like (controller dll) and hook them in using the plugin system? Or load in dlls that would allow 64 bit commands?

Share this comment


Link to comment
6 minutes ago, Wafflesoft said:

So I have a question about the LUA and DLL stuff. Does that mean I could search for things like (controller dll) and hook them in using the plugin system? Or load in dlls that would allow 64 bit commands?

1. Yes.
2. It will all be 64-bit only.

  • Like 1

Share this comment


Link to comment

OMG that is awesome news. So if its all 64 bit only, does that mean our existing projects will upgrade to 64 bit once this system is implemented? Or will we need to do that ourselves?

Share this comment


Link to comment
1 minute ago, Wafflesoft said:

OMG that is awesome news. So if its all 64 bit only, does that mean our existing projects will upgrade to 64 bit once this system is implemented? Or will we need to do that ourselves?

Your Leadwerks projects will not work with the new engine.

Share this comment


Link to comment

Here's C++ source code for a working plugin:

#ifdef _WIN32
	#define WIN32_LEAN_AND_MEAN
	#include <windows.h>
#endif

extern "C"
{
	#include <lua.h>
	#include <lauxlib.h>
	#include <lualib.h>
}

#include <sol.hpp>

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

//Example function to add to Lua API
double Add(double a, double b)
{
	return a + b;
}

//Load plugin function
bool Load(sol::state* L)
{
	L->set("TEST", 462);
	L->set_function("Add",Add);
	return true;
}

That's all it takes.

  • Like 1

Share this comment


Link to comment
4 hours ago, martyj said:

@reepblue You can compile with Visual Studio compiler using Makefiles.

Have a look at CMake.

What I was saying is that you still need MSVC to compile C++ code. Just having VSCode installed will not enable code compiling as it lacks a compiler to my knowledge.

Pretty much, can't make VSCode the official IDE because you still need MSVC on Windows and I couldn't figure out how to download only clang/g++ without XCode on macOS.

With Linux, it's not that big of a deal as the compiler is a small download away and your free to choose whatever compiler you want.

My question is why request the end user to download 2 additional pieces of software?

 

Share this comment


Link to comment

@Josh 

  1. Do you think you will use the plugin system like a nuget store for several core Turbo functions? It would make the editor easier to update without constantly building the entire editor.
  2. I do not entirely see the advantage of separating the menu logic from the importing logic. Lets say I make a C++ lib that works both in-game and in-editor, I would want to load it in via your second, but also make the menu options.
  3. How many projects are you working on right now? Don't overwork yourself.

I find the process_event callback rather cluttering up the code. How about setting a callback for a click event? 

function Plugin:Load()
  local menu = Editor.modelEditor.menu:FindMenu("Plugins")
  if menu ~= nil then
    self.menuitem = menu:AddItem("Flip Normals")

    --syntax: self.menuitem.SetMenuClickEvent(callback, extraParams = {}))
    self.menuitem:SetMenuClickEvent(
      self:FlipNormals(Editor.modelEditor.model),
      {forceModelEditorRedraw = true, someOtherValue = 0.3}
    )
  end
end

function Plugin:FlipNormals(event, params, model)
  if Editor.modelEditor.model ~= nil then
    --code: Flip normals of model
    if params.forceModelEditorRedraw then
      Editor.modelEditor:Redraw()
    end
  end
end

Rather than relying on this long if chain in processEvent, you set the callback for the events you really need like.

  • MenuClick
  • ViewPanelLeftClick
  • ViewPanelRightClick
  • SceneTreeChanged
  • TransformMoved (triggered when object in scene is moved)
  • RotateEvent (triggered when object in scene is rotated)

Share this comment


Link to comment
9 hours ago, AggrorJorn said:

@Josh 

  1. Do you think you will use the plugin system like a nuget store for several core Turbo functions? It would make the editor easier to update without constantly building the entire editor.
  2. I do not entirely see the advantage of separating the menu logic from the importing logic. Lets say I make a C++ lib that works both in-game and in-editor, I would want to load it in via your second, but also make the menu options.
  3. How many projects are you working on right now? Don't overwork yourself.

I find the process_event callback rather cluttering up the code. How about setting a callback for a click event? 

1. I have not thought about it. This isn't something I really worry about.
2. Game plugins are things that add new functionality. Editor plugins add new tools. I don't see a lot of crossover between those things.
3. Too many. There is a plan to fix this but I can't say anything more about it right now.

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Create Your Account

Sign in

Already have an account? Sign in here.

Sign In Now
×