Moho Plugin API (proof-of-concept)

Moho allows users to write new tools and plugins. Discuss scripting ideas and problems here.

Moderators: Víctor Paredes, Belgarath, slowtiger

Post Reply
User avatar
SimplSam
Posts: 1048
Joined: Thu Mar 13, 2014 5:09 pm
Location: London, UK
Contact:

Moho Plugin API (proof-of-concept)

Post by SimplSam »

Moho Plugin API - Part #1

Lua is an embedded extensible extension language with a C API. In the real world - Lua can be extended using C API ‘plugins’ to expand its feature set, and extend the hosts’ functionality and integration with external tools & processes.

To achieve a Moho Plugin system - Moho would need to operate with the C/C++ code (Moho application) controlling the Lua script library, and the Lua code then controlling a C library (plugin code). i.e. C -{api}- Lua -{api}- C. In each case the same Lua C API is used, and the plugin/s could even invoke Lua script with further C plugins … etc. etc.

Image

A real-world example of the use of the C API is the integrated Lua math library [ math.sin(), math.sqrt(), math.pi etc. ] which is written entirely as a C API add-on to the Lua core. table, string, io and os are also API add-on libraries.

The following C API code demonstrates coding examples (2 ‘add_num’ functions) - which simply add 2 numbers together:

Code: Select all

#include "lua.h" // core Lua API
#include "lauxlib.h" // auxiliary helper library (also a Lua core C API add-on)

// API accessible functions are defined with 1 parameter lua_State*, and 1 return int (# of return params)

// Function 'add_num' - with the API Lua State parameter L (which houses the Stack)
// The Stack is the data store used to exchange values between Lua and C

// Simple 'add_num' example
int add_num(lua_State *L)
{
  lua_arith (L, LUA_OPADD);  // pops 2 top values from Stack, adds them and pushes the result back onto Stack
  return 1; // return count of 'returned' values @ top of Stack
}

// More complex 'add_num' example - demonstrating direct manipulation of the Stack
__declspec(dllexport) int add_num(lua_State *L) // '__declspec(dllexport)' windows only
{
  lua_Number sum = luaL_checknumber(L, -2); // assign validated number (@top of Stack -1) to 'sum'
  sum += luaL_checknumber(L, -1); // get validated number (@top of Stack) & add to 'sum'
  lua_pushnumber(L, sum); // push 'sum' onto top of Stack
  return 1; // return count of 'returned' values @ top of Stack. Lua discards others
}
* typically source code would depend on luaxx.dll/so and be compiled to a <my_plugin>.dll/so shared binary

In Lua script a simple require() or package.loadlib() instruction will incorporate our new add_num function:

Code: Select all

local AddNum = package.loadlib(<path to my_plugin>, "add_num")
print(AddNum(5, 7))
⇒ 12
In theory - this means that Moho could exploit such an API plugin facility to register new Lua script functions and extend Moho’s feature set.

So why do we need a Plugin API when we already have Lua scripting?

Image Better, Stronger … Faster?

On the plus side Moho Plugins could offer:
  • Extended functionality beyond that which can be realised with Moho and Lua alone
  • Super fast processing, with tight integration with Lua, C and Moho
  • A vast array of C/C++ source code, know-how & lineage to exploit
  • More seamless interfacing when accessing external tools, frameworks, languages & services
On the down side Moho Plugins would be:
  • More difficult to create & maintain. Low-level compiled coding == higher learning curve and slower more complicated time-consuming development cycles
  • Less portable & more breakable. Greater need to develop & account for compatibility issues related to host platforms, OS versions and Lua binary & script versions
All in all - a Moho Plugin API would open-up a new & enticing feature-rich supplemental application ecosystem.

Case Study – “The need for speed”

Moho’s Lua scripting capability is fantastic, but just occasionally it is unable to deliver on speed, functionality or interoperability. A case in point would be: base64 encoding. Recently I had a requirement to base64 encode some images from within Moho. So I found some pure Lua code to complete this task and this worked fine… until the image source got into the 10’s of Megabytes - and the script started to ‘hang’ for a few minutes. So I sought a faster more optimal solution… and concluded that some of the best alternatives to pure Lua scripting might be:
  1. Call an external executable. This would work (current solution) but has a crude clunky, dos-popping, limited feedback, non-integrated interface
  2. Lost Marble provide new Base64 functions. A perfectly optimal integrated solution, but hugely dependent on LMs willingness and their development commitments & priorities
  3. Roll your own plugin via the C API. Another optimally performant integrated solution, dependent on own / community / free-market developer resources, but with a most-likely quick(er) turnaround. Naturally also dependent on a working Moho Plugin API
So why are we not using this Plugin API already?

The simple answer would be conflicts and compatibility. The more involved answer would be …

#1 - Virtual Machine conflicts

With Lua - each running instance executes byte-code inside a conceptual Virtual Machine. In the current implementation of Moho we have the Lua 5.2 script engine, and when Moho Lua 5.2 is used to invoke a child C API instance, the child is run in a separate or duplicate VM. For Lua 5.2 this is an unsupported configuration which can lead to memory corruption when one VM messes with the other VMs’ state.

Lua 5.2 normally detects duplicate VM scenarios and prevents duplicate VMs by issuing a ‘Multiple Lua VMs detected’ error. That prevention is great, but that prevention also halts the client VM and the calling Lua script. For the purpose of testing with current Moho - multiple Lua 5.2 VMs can minimally coexist - but only after a few tweaks and with limited data exchange and function access.

In Lua 5.2 this VM conflict can be avoided if both Host and Client Lua processes use the same VM. This requires the client to bind to and call into the host VM. This is not however currently possible with Moho - as Moho does not expose the Lua C API.

VM conflict can also be avoided by using Lua 5.4 – which states “The Lua core should work correctly with libraries using their own static copies of the same core, so there is no need to check whether they are using the same address space”. Apparently 5.4 does not have a shared access Global state. Instead - all the VM data is held inside the Lua Local State structure (which is created per client Initialization) - so there is no longer a VM conflict.

#2 - Lua version compatibility

Whilst most generic Lua script code can run on many versions of Lua with a few modifications; The ABI (Application Binary Interface) of Lua major.minor versions are not compatible. i.e. Lua 5.2.x is not binary compatible with 5.1.x nor 5.4.x. etc. So a plugin built for 5.2 would not run with a 5.3 host. We also need to take into consideration the host platform operating system. i.e. Windows / macOS.

Since Lua C API Plugins require binary compatibility, we would need to build the API Plugins at a correct/current version and hope that the Lua version embedded within Moho does not change for a while. Fortunately - Moho’s Lua version is updated infrequently. And unfortunately - Moho’s Lua version is updated infrequently. So we have great Lua version stability - but we may also not be able to exploit the features of later and greater versions - for a while …

Desperately Seeking Solutions

So lets look at what we might need to do in order to facilitate a working Moho Plugin API.

1) Current Moho 12.x / 13.5 (Lua 5.2): Proof of concept ONLY

Any Plugin API deployed in the current Moho application environment should be considered to be a volatile hack, as it is neither a stable nor supported configuration. Nonetheless - in order to create a proof-of-concept we can: match Lua versions, sneak past the multiple Lua VMs test, interact with the Lua C API, invoke custom functions (with passed and received data: numerical, string [in/out] & table [out]) and … experience some Whoop Whoop Eureka moments!

In order to achieve this hack we must:
  • Avoid using pre-built Lua binaries/libs lua52.dll/so - as they will detect & error on multiple Lua VMs
  • Download latest Moho matching Lua source code from https://www.lua.org/ftp/ (currently: Lua 5.2.4)
  • Build Lua shared library from source, after editing lauxlib.c to bypass ‘multiple Lua VMs’ check **
  • and build specific targets for Windows (dll) or macOS (so). macOS .dylibs can simply be renamed .so
  • Either incorporate the build into our plugin code or use the built Lua shared library as a dependency
** The ‘multiple Lua VMs’ check is obviously there for a reason. It stops Lua VMs conflicting with each other. Running code with the bypass could result in corrupt / invalid Moho documents etc. Thus bypassed code variants should only be used in ‘safe’ development environments.

At the moment, testing suggests that simply bypassing the duplicate VM checks can result in one Lua VM writing over or invalidating the shared memory / state of the other (possibly related to conflicting memory management) - leading to inconsistent garbled results or a Moho crash. This is most evident when we try to return complex structures (functions, userdata & tables), access Global environment (_ENV / _G) items or excessively manipulate the passed-in State & Stack.

Accessing passed variables of any data type in an API function does not appear to be a problem. However, returning structures and data to Moho can cause Lua corruption issues. In order to minimize/avoid corruption, we should:
- avoid using ‘require’ to load modules with multiple functions, properties & entry points
- load / access single functions via ‘package.loadlib’ (each will be granted its own local State)
- potentially use file I/O for non-simple variable and data structure return & transfer
- avoid accessing Global variables, functions and data via the API

2) Future Moho: LM ship Moho with update to Lua 5.4

A preferred solution would be for Lost Marble to upgrade the embedded Lua version to 5.4. Lua 5.4 is multi-VM friendly, thus eliminating the multiple Lua VMs compatibility issue. Plugins would still need to run a matching Lua binary version and be updated to maintain version parity if/when Moho’s Lua version is next updated.

Naturally - updating the Lua version to 5.4 may also necessitate some refactoring and updates to existing Lua scripts.

3) Current/Future Moho: LM expose the Lua C API

If we are unable to upgrade to Lua 5.4 (any time soon), it may be possible that Lost Marble could provide access to the Moho Lua C API via shared DLL or other mechanism, so that C API Plugins can bind to the same main Lua Virtual Machine as used by Moho - eliminating the multiple Lua VMs compatibility issue. Plugins would need to be updated to maintain binary version parity if/when the Moho Lua version is updated.

--------------------------------------------------------------------------------

What Next?

In Part #2 - I will provide dll/so binaries, code and instructions on how to build and/or run working prototypes.

Feedback, Comments & Corrections are naturally welcome.

Further Reading

Glossary

Some hopefully useful info on some key component terms …

Environment (_ENV) - The global scope table (since Lua 5.2). By default everything that is considered to be at global scope in Lua is in the _ENV table, and when we execute statements like: print, we are really executing _ENV.print(). It is initialised to a default Global Environment, but you can set your own environment by assigning _ENV. i.e. _ENV = {} (only affects your own code). If you did that assignment then following functions like print will stop working (be inaccessible) as they are no longer in the new scope. Groups of functions can be set to share a private ‘global’ environment.

Global (_G) - _G is a global variable set to point to _ENV by default. _ENV._G ==> _ENV. It can be re-assigned just like _ENV, in fact it can potentially be reassigned to any type or value - as Lua does not use _G. 3rd party scripts may however use it.

Global State - Sometimes referred to as global state machine - relates to the operation of the Lua Virtual Machine. It is a set of structures, processes and values defined & utilised for garbage collection, memory and state management. From the perspective of Lua code, the global_State structure is completely inaccessible as we don’t need to access to it. It is however crucially important and manages globally unique state information in Lua.

Lua State - A unique data structure assigned to each Lua processing thread, which holds all the info about that running/runnable thread. Each Lua State also has a pointer to Global State and to a ‘global’ environment (_ENV). The State is passed (by reference) between Lua and C, and also between C API process calls. The State also houses a Stack.

Lua Thread - A line of execution with its own Stack, local variables and potentially ‘global’ environment. A Plugin would run in its own thread. Lua is single threaded, so only one thread can execute at any given time. Lua also has a mechanism called coroutines that allow threads to yield & resume processing - emulating multi-threading (executing multiple processes concurrently).

Lua Virtual Machine - Lua runs programs by first compiling them into instructions (bytecode & opcodes), then interprets and executes those instructions with a virtual processor, whilst ensuring correct state and memory management. The entire mechanism for completing these functions is the Virtual Machine.

The Stack - An abstract stacked data store used to exchange values between Lua and C. It can be thought of as a stack of pizza boxes. Each box in this stack can hold any Lua value or nothing. Whenever you (your C app) needs a value from Lua (such as a parameter or the value of a global variable), Lua has to push the required value onto the stack - so that your app can pop it off and/or use it later. Whenever you want to pass values from C to Lua, you first have to push the values onto the stack, so that Lua can pop them off.

Image

Lua manipulates this stack in a strict LIFO order (Last In, First Out; via the top). Lua can only change the topmost part of the stack. Whereas your plugin C code can inspect any element inside the stack and insert and delete elements in any arbitrary position. The bottom of the stack has index 1, and the top is ‘no. of elements’ or -1. p.s. In Lua script you never interact with the stack directly - it is all done on your behalf.
Last edited by SimplSam on Wed Feb 02, 2022 8:31 pm, edited 2 times in total.
Moho 14.1 » Win 11 Pro 64GB » NVIDIA GTX 1080ti 11GB
Moho 14.1 » Mac mini 2012 8GB » macOS 10.15 Catalina
Tube: SimplSam


Sam
Stan
Posts: 199
Joined: Sun Apr 19, 2009 3:22 pm

Re: Moho Plugin API (proof-of-concept)

Post by Stan »

Thank you for bringing this up, Sam! I'm really looking forward for part 2.
________________________________________________________________________
https://mohoscripting.com/ - Unofficial Moho Lua scripting documentation
https://mohoscripts.com/ - The best place to publish and download scripts for Moho
User avatar
hayasidist
Posts: 3525
Joined: Wed Feb 16, 2011 8:12 pm
Location: Kent, England

Re: Moho Plugin API (proof-of-concept)

Post by hayasidist »

Very interesting!


One of the "side threads" I've been looking at from time to time (last time was a while ago!) was the way that the MOHO.RunUpdateTable(moho) function invokes the routines that are stored in MOHO.UpdateTable

In a nutshell, if I add a function to the UpdateTable (e.g.) table.insert(MOHO.UpdateTable, HS_ForUpdate)

then that function, HS_ForUpdate(moho), gets called when expected and it can access methods in the script interface (e.g. IsPro())

but it I can't get it to access attributes in the script interface (e.g. frame)


more here...

http://www.lostmarble.com/forum/viewtop ... 30#p198597

What would be truly superb would be some "c stuff" either
- to call from LUA to access attributes (e.g. f = C_GetAttribute("frame")); or
- that can call a LUA script, passing it a "moho" that it can use (e.g. pushStuff (moho_for_lua); LUAfunc() [or whatever the right syntax is! - apologies in advance for my ignorance here] )
User avatar
SimplSam
Posts: 1048
Joined: Thu Mar 13, 2014 5:09 pm
Location: London, UK
Contact:

Re: Moho Plugin API (proof-of-concept)

Post by SimplSam »

hayasidist wrote: Sat Jan 29, 2022 6:57 pm ...
What would be truly superb would be some "c stuff" either
- to call from LUA to access attributes (e.g. f = C_GetAttribute("frame")); or
- that can call a LUA script, passing it a "moho" that it can use (e.g. pushStuff (moho_for_lua); LUAfunc() [or whatever the right syntax is! - apologies in advance for my ignorance here] )
I don't think you would be able to access anything has not been correctly exposed by the Moho app. You should be able to access / execute some Moho functions & features, but only if they are already available via passed in parameters or if they are in Global scope.

One thing I saw in the crash_log on Mac when trying to access MOHO.ScriptInterface.frame was a call to tolua_get_ScriptInterface_MOHO_fFrame(lua_State*) - which caused the crash with Access Violation (KERN_INVALID_ADDRESS at 0x0000000000000000). Similarly tolua_get_ScriptInterface_MOHO_fLayer_ptr(lua_State*) for MOHO.ScriptInterface.layer. It seems that those simple .frame & .layer references invoke special C functions that are not yet correctly initialized.

Code: Select all

0   com.lostmarble.moho           	0x000000010d125274 tolua_get_ScriptInterface_MOHO_fFrame(lua_State*) + 68
1   com.lostmarble.moho           	0x000000010d9b99f1 luaD_precall + 769
2   com.lostmarble.moho           	0x000000010db0e7a1 class_index_event + 2785
3   com.lostmarble.moho           	0x000000010d9b99f1 luaD_precall + 769
4   com.lostmarble.moho           	0x000000010d9b9e3b luaD_call + 59
5   com.lostmarble.moho           	0x000000010d9de858 luaV_gettable + 536
6   com.lostmarble.moho           	0x000000010d9e0d84 luaV_execute + 3268
7   com.lostmarble.moho           	0x000000010d9a9917 f_call + 71
8   com.lostmarble.moho           	0x000000010d9b9366 luaD_rawrunprotected + 86
9   com.lostmarble.moho           	0x000000010d9ba207 luaD_pcall + 55
10  com.lostmarble.moho           	0x000000010d548f49 LM_Lua::CompleteCall() + 201
11  com.lostmarble.moho           	0x000000010d2538ae MainLayer::HandleMessage(LM_Message*) + 3438
12  com.lostmarble.moho           	0x000000010d5898a2 LM_BaseWidget::SendMessage(int) + 82
13  com.lostmarble.moho           	0x000000010d58c576 LM_Button::OnMouseUp(LM_Point, unsigned int, unsigned int, LM_Message*) + 966
14  com.lostmarble.moho           	0x000000010d51fa72 LM_View::_OnMouseUp(LM_Point, unsigned int, unsigned int, LM_Message*) + 290
Moho 14.1 » Win 11 Pro 64GB » NVIDIA GTX 1080ti 11GB
Moho 14.1 » Mac mini 2012 8GB » macOS 10.15 Catalina
Tube: SimplSam


Sam
User avatar
hayasidist
Posts: 3525
Joined: Wed Feb 16, 2011 8:12 pm
Location: Kent, England

Re: Moho Plugin API (proof-of-concept)

Post by hayasidist »

Thanks for the info. It is (I guess) down to the .get

And that's really why I was wondering if a written-in-C function was the way forward ... i.e. the thing that gets added to the MOHO.UpdateTable is written in C and is passed a "moho" that is usable in C (because if that routine is written in Lua I can't make the moho that is passed to it work.)

any thoughts?
User avatar
SimplSam
Posts: 1048
Joined: Thu Mar 13, 2014 5:09 pm
Location: London, UK
Contact:

Re: Moho Plugin API (proof-of-concept)

Post by SimplSam »

Ultimately moho (scriptinterface) is not in the right state - so it does not matter where you access it from. LM would need to provide access to either a stable moho variant or alternate access to Moho properties like current frame & layer.

moho is userdata, and if you monitor it in any IsEnabled function (as below) - you will see that its address is constantly (frequently) changing as you move around tools, layers and frames etc. Indicating that is Initialized -> useuable, Used, Finalized -> unuseable.

Code: Select all

if (mymoho ~= moho) then
    print("  moho is: ", tostring(moho))
    mymoho = moho
end
Moho 14.1 » Win 11 Pro 64GB » NVIDIA GTX 1080ti 11GB
Moho 14.1 » Mac mini 2012 8GB » macOS 10.15 Catalina
Tube: SimplSam


Sam
Defims
Posts: 19
Joined: Sun May 30, 2010 11:34 am
Contact:

Re: Moho Plugin API (proof-of-concept)

Post by Defims »

WOW! nice work! Is it posible to load a dll which include lua54 header file?
Defims
Posts: 19
Joined: Sun May 30, 2010 11:34 am
Contact:

Re: Moho Plugin API (proof-of-concept)

Post by Defims »

Moho 14 has updated the scripting Interface to Lua 5.4.4! Write your own tools, modify the existing ones or check out what other users have created. There are hundreds of powerful tools and improvements created by the community. Make Moho work exactly the way you want!
Which means powerful plugins are coming?
User avatar
SimplSam
Posts: 1048
Joined: Thu Mar 13, 2014 5:09 pm
Location: London, UK
Contact:

Re: Moho Plugin API (proof-of-concept)

Post by SimplSam »

Defims wrote: Tue Sep 12, 2023 3:50 pm
Moho 14 has updated the scripting Interface to Lua 5.4.4! Write your own tools, modify the existing ones or check out what other users have created. There are hundreds of powerful tools and improvements created by the community. Make Moho work exactly the way you want!
Which means powerful plugins are coming?
Hopefully... I just wrote the following on Discord:

"The upgrade to Lua 5.4 allows Moho to more seamlessly interoperate with the host computer system, external apps, remote systems and other scripting languages like Python - via C API addons. This requires more programming effort - but hopefully will eventually yield some very next-level addon 'possibilities' for collaboration, media handling, import/export processes, UI extensions and automation."
Moho 14.1 » Win 11 Pro 64GB » NVIDIA GTX 1080ti 11GB
Moho 14.1 » Mac mini 2012 8GB » macOS 10.15 Catalina
Tube: SimplSam


Sam
Post Reply