commit ac521356cc1d6dee648287cfa64dd007291bed02 Author: saarsena@gmail.com Date: Fri Apr 24 21:40:08 2026 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df2503c --- /dev/null +++ b/.gitignore @@ -0,0 +1,108 @@ +# ----------------- # +# Godot 4+ specific # +# ----------------- # + +# This is the most important folder to ignore. It contains the project's cache, +# imported asset data, and other generated files. It will be regenerated +# automatically by the editor. Committing it will bloat the repository and +# cause constant merge conflicts. +.godot/ +.claude +.vscode +backup + +# Ignore user-specific editor settings. +# These files save the state of your editor (window layout, last open scenes, etc.) +# and should not be shared with the team. +.godot/editor/ +editor_layout.cfg +command_system_guide.md +*.md + +# ------------- # +# C# / .NET # +# ------------- # + +# Ignore the .mono directory, which contains Mono-related build files and cache for Godot 3.x. +# For Godot 4.x with .NET 6+, these files are typically inside .godot/mono +.mono/ + +# Ignore generated build artifacts from dotnet/MSBuild. +bin/ +obj/ + +# Ignore user-specific C# project files created by IDEs like Visual Studio or Rider. +*.csproj.user +*.sln.DotSettings.user + + +# -------------------------- # +# Exported Builds & Templates # +# -------------------------- # + +# Ignore directories where you might store exported builds of your game. +builds/ +export/ +exports/ + +# Ignore the actual exported game files, regardless of where they are. +*.pck +*.exe +*.AppImage +*.html +*.wasm +*.apk +*.aab + +# Ignore export templates, which are large and can be downloaded by any developer. +# You typically don't want these in your repository. +export_templates/ +templates/ +*.zip +*.tpz + + +# ----------------------------------- # +# IDE / Text Editor Specific Settings # +# ----------------------------------- # + +# Visual Studio Code +.vscode/ + +# JetBrains Rider / IntelliJ +.idea/ + +# Visual Studio +.vs/ + + +# ---------------- # +# OS Specific # +# ---------------- # + +# macOS +.DS_Store +.AppleDouble +.LSOverride + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini + + +# ---------------- # +# Other Common # +# ---------------- # + +# Log files +debug_log.txt +*.log +turn_system_plan.md + +# Asset pack archives (you should unpack them and commit the assets, not the zip) +*.rar +*.7z + + +.CLAUDE.md diff --git a/.sconsign.dblite b/.sconsign.dblite new file mode 100644 index 0000000..080eafb Binary files /dev/null and b/.sconsign.dblite differ diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..3191f01 --- /dev/null +++ b/SConstruct @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +env = SConscript("godot-cpp/SConstruct") + +env.Append(CPPPATH=["src"]) +env.ParseConfig("pkg-config lua-5.1 --cflags --libs") + +sources = Glob("src/*.cpp") + +if env["platform"] == "macos": + library = env.SharedLibrary( + "demo/addons/onebit_lua/bin/libonebit_lua.{}.{}.framework/libonebit_lua.{}.{}".format( + env["platform"], env["target"], env["platform"], env["target"] + ), + source=sources, + ) +else: + library = env.SharedLibrary( + "demo/addons/onebit_lua/bin/libonebit_lua{}{}".format(env["suffix"], env["SHLIBSUFFIX"]), + source=sources, + ) + +env.NoCache(library) +Default(library) diff --git a/demo/addons/onebit_lua/onebit_lua.gdextension b/demo/addons/onebit_lua/onebit_lua.gdextension new file mode 100644 index 0000000..5a3b911 --- /dev/null +++ b/demo/addons/onebit_lua/onebit_lua.gdextension @@ -0,0 +1,9 @@ +[configuration] + +entry_symbol = "onebit_lua_library_init" +compatibility_minimum = "4.6" + +[libraries] + +linux.debug.x86_64 = "res://addons/onebit_lua/bin/libonebit_lua.linux.template_debug.x86_64.so" +linux.release.x86_64 = "res://addons/onebit_lua/bin/libonebit_lua.linux.template_release.x86_64.so" diff --git a/demo/addons/onebit_lua/onebit_lua.gdextension.uid b/demo/addons/onebit_lua/onebit_lua.gdextension.uid new file mode 100644 index 0000000..32ed0c4 --- /dev/null +++ b/demo/addons/onebit_lua/onebit_lua.gdextension.uid @@ -0,0 +1 @@ +uid://cokyuf5lvnqgw diff --git a/demo/project.godot b/demo/project.godot new file mode 100644 index 0000000..2e83670 --- /dev/null +++ b/demo/project.godot @@ -0,0 +1,11 @@ +config_version=5 + +[application] + +config/name="OneBit Lua Runtime Demo" +run/main_scene="res://test_runtime.tscn" +config/features=PackedStringArray("4.6") + +[native_extensions] + +paths=["res://addons/onebit_lua/onebit_lua.gdextension"] diff --git a/demo/test_runtime.gd b/demo/test_runtime.gd new file mode 100644 index 0000000..059c9d5 --- /dev/null +++ b/demo/test_runtime.gd @@ -0,0 +1,70 @@ +extends Node + + +class QueryHost: + extends RefCounted + + func lua_query(_entity_id: int, query_name: String, params: Dictionary) -> Variant: + print("lua_query: ", query_name, " ", params) + match query_name: + "sense.is_walkable": + return params.get("x", 0) == 2 and params.get("y", 0) == 3 + "sense.find_nearest": + return { + "entity_id": 7, + "distance": 4, + "world_x": 32.0, + "world_y": 48.0, + "group": params.get("group", ""), + } + _: + return null + + +var query_host := QueryHost.new() + + +func _ready() -> void: + var rt := OneBitLuaRuntime.new() + rt.set_entity_id(42) + rt.set_query_host(query_host) + rt.set_instruction_budget(10000) + + var loaded := rt.load_string(""" +return { + api_version = 2, + + on_update = function(ctx, state) + state.count = (state.count or 0) + 1 + if not ctx.self.is_moving and sense.is_walkable(2, 3) then + local nearest = sense.find_nearest("players") + move.step(1, 0) + log.say("tick", 1.0) + end + end +} +""") + if not loaded.ok: + push_error("load_string failed: " + str(loaded.error)) + get_tree().quit(1) + return + + var ctx := { + "self": { + "is_moving": false, + }, + } + var state := {} + var result := rt.call_event("on_update", ctx, state, []) + if not result.ok: + push_error("call_event failed: " + str(result.error)) + get_tree().quit(1) + return + + if result.commands.size() != 1 or result.logs.size() != 1 or result.state.get("count", 0) != 1: + push_error("unexpected result: " + str(result)) + get_tree().quit(1) + return + + print("OneBitLuaRuntime smoke test passed: ", result) + get_tree().quit(0) diff --git a/demo/test_runtime.gd.uid b/demo/test_runtime.gd.uid new file mode 100644 index 0000000..a0acda5 --- /dev/null +++ b/demo/test_runtime.gd.uid @@ -0,0 +1 @@ +uid://du60k7ew85w0j diff --git a/demo/test_runtime.tscn b/demo/test_runtime.tscn new file mode 100644 index 0000000..bcc2b81 --- /dev/null +++ b/demo/test_runtime.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://test_runtime.gd" id="1"] + +[node name="TestRuntime" type="Node"] +script = ExtResource("1") diff --git a/godot-cpp b/godot-cpp new file mode 160000 index 0000000..973a98f --- /dev/null +++ b/godot-cpp @@ -0,0 +1 @@ +Subproject commit 973a98f9b877327a5f51abe58e035bf7eeabf3e4 diff --git a/src/onebit_lua_runtime.cpp b/src/onebit_lua_runtime.cpp new file mode 100644 index 0000000..429be15 --- /dev/null +++ b/src/onebit_lua_runtime.cpp @@ -0,0 +1,406 @@ +#include "onebit_lua_runtime.h" + +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +#ifndef LUA_OK +#define LUA_OK 0 +#endif + +using namespace godot; + +void OneBitLuaRuntime::_bind_methods() { + ClassDB::bind_method(D_METHOD("load_string", "source"), &OneBitLuaRuntime::load_string); + ClassDB::bind_method(D_METHOD("load_script", "path"), &OneBitLuaRuntime::load_script); + ClassDB::bind_method(D_METHOD("call_event", "event_name", "ctx", "state", "args"), &OneBitLuaRuntime::call_event, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("set_entity_id", "entity_id"), &OneBitLuaRuntime::set_entity_id); + ClassDB::bind_method(D_METHOD("set_query_host", "host"), &OneBitLuaRuntime::set_query_host); + ClassDB::bind_method(D_METHOD("set_instruction_budget", "budget"), &OneBitLuaRuntime::set_instruction_budget); +} + +OneBitLuaRuntime::OneBitLuaRuntime() { + _reset_lua(); +} + +OneBitLuaRuntime::~OneBitLuaRuntime() { + _reset_lua(); +} + +void OneBitLuaRuntime::_reset_lua() { + if (lua) { + _clear_handlers(); + lua_close(lua); + lua = nullptr; + } + + lua = luaL_newstate(); + instruction_budget_exceeded = false; + _install_api(); +} + +void OneBitLuaRuntime::_clear_handlers() { + if (!lua) { + handlers.clear(); + return; + } + + for (KeyValue &entry : handlers) { + luaL_unref(lua, LUA_REGISTRYINDEX, entry.value); + } + handlers.clear(); +} + +void OneBitLuaRuntime::_install_api() { + lua_pushlightuserdata(lua, this); + lua_setglobal(lua, "__onebit_runtime"); + + lua_newtable(lua); + lua_pushlightuserdata(lua, this); + lua_pushcclosure(lua, &OneBitLuaRuntime::_lua_sense_is_walkable, 1); + lua_setfield(lua, -2, "is_walkable"); + lua_pushlightuserdata(lua, this); + lua_pushcclosure(lua, &OneBitLuaRuntime::_lua_sense_find_nearest, 1); + lua_setfield(lua, -2, "find_nearest"); + lua_setglobal(lua, "sense"); + + lua_newtable(lua); + lua_pushlightuserdata(lua, this); + lua_pushcclosure(lua, &OneBitLuaRuntime::_lua_move_step, 1); + lua_setfield(lua, -2, "step"); + lua_setglobal(lua, "move"); + + lua_newtable(lua); + lua_pushlightuserdata(lua, this); + lua_pushcclosure(lua, &OneBitLuaRuntime::_lua_log_say, 1); + lua_setfield(lua, -2, "say"); + lua_setglobal(lua, "log"); +} + +Dictionary OneBitLuaRuntime::_base_result(bool ok, const String &error) const { + Dictionary result; + result["ok"] = ok; + result["api_version"] = 2; + result["commands"] = commands; + result["state"] = Dictionary(); + result["logs"] = logs; + result["error"] = error; + return result; +} + +Dictionary OneBitLuaRuntime::_error_result(const String &error) const { + return _base_result(false, error); +} + +Dictionary OneBitLuaRuntime::load_string(const String &source) { + _reset_lua(); + commands.clear(); + logs.clear(); + + CharString utf8 = source.utf8(); + if (luaL_loadstring(lua, utf8.get_data()) != LUA_OK) { + String error = String(lua_tostring(lua, -1)); + lua_pop(lua, 1); + return _error_result(error); + } + + if (lua_pcall(lua, 0, 1, 0) != LUA_OK) { + String error = String(lua_tostring(lua, -1)); + lua_pop(lua, 1); + return _error_result(error); + } + + if (!lua_istable(lua, -1)) { + lua_pop(lua, 1); + return _error_result("Lua script must return a handler table"); + } + + Dictionary result = _extract_handlers(lua_gettop(lua), ""); + lua_pop(lua, 1); + return result; +} + +Dictionary OneBitLuaRuntime::load_script(const String &path) { + Ref file = FileAccess::open(path, FileAccess::READ); + if (file.is_null()) { + return _error_result("Could not open Lua script: " + path); + } + + String source = file->get_as_text(); + return load_string(source); +} + +Dictionary OneBitLuaRuntime::_extract_handlers(int table_index, const String &source_name) { + lua_getfield(lua, table_index, "api_version"); + if (!lua_isnumber(lua, -1) || int(lua_tointeger(lua, -1)) != 2) { + lua_pop(lua, 1); + return _error_result("Lua script " + source_name + " must return api_version = 2"); + } + lua_pop(lua, 1); + + _clear_handlers(); + lua_pushnil(lua); + while (lua_next(lua, table_index) != 0) { + if (lua_type(lua, -2) == LUA_TSTRING && lua_isfunction(lua, -1)) { + String name = String(lua_tostring(lua, -2)); + lua_pushvalue(lua, -1); + handlers[name] = luaL_ref(lua, LUA_REGISTRYINDEX); + } + lua_pop(lua, 1); + } + + Dictionary result = _base_result(true); + Array handler_names; + for (const KeyValue &entry : handlers) { + handler_names.append(entry.key); + } + result["handlers"] = handler_names; + return result; +} + +Dictionary OneBitLuaRuntime::call_event(const String &event_name, const Dictionary &ctx, const Dictionary &state, const Array &args) { + commands.clear(); + logs.clear(); + instruction_budget_exceeded = false; + + if (!handlers.has(event_name)) { + return _error_result("No Lua handler registered for event: " + event_name); + } + + lua_rawgeti(lua, LUA_REGISTRYINDEX, handlers[event_name]); + if (!_push_variant(ctx)) { + lua_pop(lua, 1); + return _error_result("Failed to push ctx table"); + } + if (!_push_variant(state)) { + lua_pop(lua, 2); + return _error_result("Failed to push state table"); + } + + lua_pushvalue(lua, -1); + int state_ref = luaL_ref(lua, LUA_REGISTRYINDEX); + + for (int64_t i = 0; i < args.size(); i++) { + _push_variant(args[i]); + } + + lua_sethook(lua, &OneBitLuaRuntime::_instruction_hook, LUA_MASKCOUNT, int(instruction_budget)); + int status = lua_pcall(lua, 2 + int(args.size()), 0, 0); + lua_sethook(lua, nullptr, 0, 0); + + if (status != LUA_OK) { + String error = instruction_budget_exceeded ? String("Lua instruction budget exceeded") : String(lua_tostring(lua, -1)); + lua_pop(lua, 1); + luaL_unref(lua, LUA_REGISTRYINDEX, state_ref); + return _error_result(error); + } + + lua_rawgeti(lua, LUA_REGISTRYINDEX, state_ref); + Variant updated_state = _read_lua_value(-1); + lua_pop(lua, 1); + luaL_unref(lua, LUA_REGISTRYINDEX, state_ref); + + Dictionary result = _base_result(true); + result["commands"] = commands; + result["logs"] = logs; + result["state"] = updated_state; + return result; +} + +void OneBitLuaRuntime::set_entity_id(int64_t p_entity_id) { + entity_id = p_entity_id; +} + +void OneBitLuaRuntime::set_query_host(Object *p_host) { + query_host_id = p_host ? p_host->get_instance_id() : 0; +} + +void OneBitLuaRuntime::set_instruction_budget(int64_t p_budget) { + instruction_budget = p_budget > 0 ? p_budget : 1; +} + +void OneBitLuaRuntime::_instruction_hook(lua_State *L, lua_Debug *debug) { + (void)debug; + OneBitLuaRuntime *runtime = nullptr; + lua_getglobal(L, "__onebit_runtime"); + if (lua_islightuserdata(L, -1)) { + runtime = static_cast(lua_touserdata(L, -1)); + } + lua_pop(L, 1); + + if (runtime) { + runtime->instruction_budget_exceeded = true; + } + luaL_error(L, "Lua instruction budget exceeded"); +} + +Variant OneBitLuaRuntime::_query(const String &query_name, const Dictionary ¶ms) { + if (query_host_id == 0) { + return Variant(); + } + + Object *query_host = ObjectDB::get_instance(query_host_id); + if (!query_host) { + return Variant(); + } + return query_host->call("lua_query", entity_id, query_name, params); +} + +void OneBitLuaRuntime::_append_move_step(int dx, int dy) { + Dictionary params; + params["direction"] = Vector2i(dx, dy); + + Dictionary command; + command["type"] = "move"; + command["entity_id"] = entity_id; + command["params"] = params; + commands.append(command); +} + +void OneBitLuaRuntime::_append_log_say(const String &message, double duration) { + Dictionary log_entry; + log_entry["message"] = message; + log_entry["duration"] = duration; + logs.append(log_entry); +} + +int OneBitLuaRuntime::_lua_move_step(lua_State *L) { + OneBitLuaRuntime *runtime = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + int dx = int(luaL_checkinteger(L, 1)); + int dy = int(luaL_checkinteger(L, 2)); + runtime->_append_move_step(dx, dy); + lua_pushboolean(L, 1); + return 1; +} + +int OneBitLuaRuntime::_lua_log_say(lua_State *L) { + OneBitLuaRuntime *runtime = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + String message = String(luaL_checkstring(L, 1)); + double duration = luaL_optnumber(L, 2, 3.0); + runtime->_append_log_say(message, duration); + lua_pushboolean(L, 1); + return 1; +} + +int OneBitLuaRuntime::_lua_sense_is_walkable(lua_State *L) { + OneBitLuaRuntime *runtime = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + Dictionary params; + params["x"] = int(luaL_checkinteger(L, 1)); + params["y"] = int(luaL_checkinteger(L, 2)); + Variant result = runtime->_query("sense.is_walkable", params); + lua_pushboolean(L, result.get_type() == Variant::BOOL && bool(result)); + return 1; +} + +int OneBitLuaRuntime::_lua_sense_find_nearest(lua_State *L) { + OneBitLuaRuntime *runtime = static_cast(lua_touserdata(L, lua_upvalueindex(1))); + Dictionary params; + params["group"] = String(luaL_checkstring(L, 1)); + Variant result = runtime->_query("sense.find_nearest", params); + if (!runtime->_push_variant(result)) { + lua_pushnil(L); + } + return 1; +} + +bool OneBitLuaRuntime::_push_variant(const Variant &value) { + switch (value.get_type()) { + case Variant::NIL: + lua_pushnil(lua); + return true; + case Variant::BOOL: + lua_pushboolean(lua, bool(value)); + return true; + case Variant::INT: + lua_pushinteger(lua, int64_t(value)); + return true; + case Variant::FLOAT: + lua_pushnumber(lua, double(value)); + return true; + case Variant::STRING: { + CharString utf8 = String(value).utf8(); + lua_pushstring(lua, utf8.get_data()); + return true; + } + case Variant::VECTOR2I: { + Vector2i vector = value; + lua_newtable(lua); + lua_pushinteger(lua, vector.x); + lua_setfield(lua, -2, "x"); + lua_pushinteger(lua, vector.y); + lua_setfield(lua, -2, "y"); + return true; + } + case Variant::DICTIONARY: { + Dictionary dict = value; + lua_newtable(lua); + Array keys = dict.keys(); + for (int64_t i = 0; i < keys.size(); i++) { + _push_variant(keys[i]); + _push_variant(dict[keys[i]]); + lua_settable(lua, -3); + } + return true; + } + case Variant::ARRAY: { + Array array = value; + lua_newtable(lua); + for (int64_t i = 0; i < array.size(); i++) { + lua_pushinteger(lua, i + 1); + _push_variant(array[i]); + lua_settable(lua, -3); + } + return true; + } + default: + lua_pushnil(lua); + return false; + } +} + +Variant OneBitLuaRuntime::_read_lua_value(int index, int depth) { + if (depth > 16) { + return Variant(); + } + + int value_type = lua_type(lua, index); + switch (value_type) { + case LUA_TNIL: + return Variant(); + case LUA_TBOOLEAN: + return bool(lua_toboolean(lua, index)); + case LUA_TNUMBER: + return double(lua_tonumber(lua, index)); + case LUA_TSTRING: + return String(lua_tostring(lua, index)); + case LUA_TTABLE: + return _read_lua_table(index, depth + 1); + default: + return Variant(); + } +} + +Dictionary OneBitLuaRuntime::_read_lua_table(int index, int depth) { + Dictionary dict; + int abs_index = index; + if (abs_index < 0) { + abs_index = lua_gettop(lua) + abs_index + 1; + } + + lua_pushnil(lua); + while (lua_next(lua, abs_index) != 0) { + Variant key = _read_lua_value(-2, depth + 1); + Variant value = _read_lua_value(-1, depth + 1); + dict[key] = value; + lua_pop(lua, 1); + } + + return dict; +} diff --git a/src/onebit_lua_runtime.h b/src/onebit_lua_runtime.h new file mode 100644 index 0000000..bfd11f8 --- /dev/null +++ b/src/onebit_lua_runtime.h @@ -0,0 +1,65 @@ +#ifndef ONEBIT_LUA_RUNTIME_H +#define ONEBIT_LUA_RUNTIME_H + +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +using namespace godot; + +class OneBitLuaRuntime : public RefCounted { + GDCLASS(OneBitLuaRuntime, RefCounted) + +private: + lua_State *lua = nullptr; + HashMap handlers; + Array commands; + Array logs; + int64_t entity_id = 0; + uint64_t query_host_id = 0; + int64_t instruction_budget = 100000; + bool instruction_budget_exceeded = false; + + static void _bind_methods(); + + static int _lua_move_step(lua_State *L); + static int _lua_log_say(lua_State *L); + static int _lua_sense_is_walkable(lua_State *L); + static int _lua_sense_find_nearest(lua_State *L); + static void _instruction_hook(lua_State *L, lua_Debug *debug); + + void _reset_lua(); + void _install_api(); + void _clear_handlers(); + Dictionary _base_result(bool ok, const String &error = "") const; + Dictionary _error_result(const String &error) const; + Variant _query(const String &query_name, const Dictionary ¶ms); + void _append_move_step(int dx, int dy); + void _append_log_say(const String &message, double duration); + Dictionary _extract_handlers(int table_index, const String &source_name); + bool _push_variant(const Variant &value); + Variant _read_lua_value(int index, int depth = 0); + Dictionary _read_lua_table(int index, int depth = 0); + +public: + OneBitLuaRuntime(); + ~OneBitLuaRuntime(); + + Dictionary load_string(const String &source); + Dictionary load_script(const String &path); + Dictionary call_event(const String &event_name, const Dictionary &ctx, const Dictionary &state, const Array &args = Array()); + void set_entity_id(int64_t p_entity_id); + void set_query_host(Object *p_host); + void set_instruction_budget(int64_t p_budget); +}; + +#endif diff --git a/src/onebit_lua_runtime.os b/src/onebit_lua_runtime.os new file mode 100644 index 0000000..178190f Binary files /dev/null and b/src/onebit_lua_runtime.os differ diff --git a/src/register_types.cpp b/src/register_types.cpp new file mode 100644 index 0000000..a55aca3 --- /dev/null +++ b/src/register_types.cpp @@ -0,0 +1,38 @@ +#include "register_types.h" + +#include "onebit_lua_runtime.h" + +#include +#include +#include + +using namespace godot; + +void initialize_onebit_lua_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } + + GDREGISTER_CLASS(OneBitLuaRuntime); +} + +void uninitialize_onebit_lua_module(ModuleInitializationLevel p_level) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + return; + } +} + +extern "C" { +GDExtensionBool GDE_EXPORT onebit_lua_library_init( + GDExtensionInterfaceGetProcAddress p_get_proc_address, + GDExtensionClassLibraryPtr p_library, + GDExtensionInitialization *r_initialization) { + godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); + + init_obj.register_initializer(initialize_onebit_lua_module); + init_obj.register_terminator(uninitialize_onebit_lua_module); + init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); + + return init_obj.init(); +} +} diff --git a/src/register_types.h b/src/register_types.h new file mode 100644 index 0000000..612f031 --- /dev/null +++ b/src/register_types.h @@ -0,0 +1,11 @@ +#ifndef ONEBIT_LUA_REGISTER_TYPES_H +#define ONEBIT_LUA_REGISTER_TYPES_H + +#include + +using namespace godot; + +void initialize_onebit_lua_module(ModuleInitializationLevel p_level); +void uninitialize_onebit_lua_module(ModuleInitializationLevel p_level); + +#endif diff --git a/src/register_types.os b/src/register_types.os new file mode 100644 index 0000000..9160d44 Binary files /dev/null and b/src/register_types.os differ