initial commit
This commit is contained in:
commit
ac521356cc
16 changed files with 751 additions and 0 deletions
108
.gitignore
vendored
Normal file
108
.gitignore
vendored
Normal file
|
|
@ -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
|
||||
BIN
.sconsign.dblite
Normal file
BIN
.sconsign.dblite
Normal file
Binary file not shown.
24
SConstruct
Normal file
24
SConstruct
Normal file
|
|
@ -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)
|
||||
9
demo/addons/onebit_lua/onebit_lua.gdextension
Normal file
9
demo/addons/onebit_lua/onebit_lua.gdextension
Normal file
|
|
@ -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"
|
||||
1
demo/addons/onebit_lua/onebit_lua.gdextension.uid
Normal file
1
demo/addons/onebit_lua/onebit_lua.gdextension.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cokyuf5lvnqgw
|
||||
11
demo/project.godot
Normal file
11
demo/project.godot
Normal file
|
|
@ -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"]
|
||||
70
demo/test_runtime.gd
Normal file
70
demo/test_runtime.gd
Normal file
|
|
@ -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)
|
||||
1
demo/test_runtime.gd.uid
Normal file
1
demo/test_runtime.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://du60k7ew85w0j
|
||||
6
demo/test_runtime.tscn
Normal file
6
demo/test_runtime.tscn
Normal file
|
|
@ -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")
|
||||
1
godot-cpp
Submodule
1
godot-cpp
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 973a98f9b877327a5f51abe58e035bf7eeabf3e4
|
||||
406
src/onebit_lua_runtime.cpp
Normal file
406
src/onebit_lua_runtime.cpp
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
#include "onebit_lua_runtime.h"
|
||||
|
||||
#include <godot_cpp/classes/file_access.hpp>
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
#include <godot_cpp/core/object.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
#include <godot_cpp/variant/vector2i.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <lauxlib.h>
|
||||
#include <lualib.h>
|
||||
}
|
||||
|
||||
#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<String, int> &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), "<string>");
|
||||
lua_pop(lua, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
Dictionary OneBitLuaRuntime::load_script(const String &path) {
|
||||
Ref<FileAccess> 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<String, int> &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<OneBitLuaRuntime *>(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<OneBitLuaRuntime *>(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<OneBitLuaRuntime *>(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<OneBitLuaRuntime *>(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<OneBitLuaRuntime *>(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;
|
||||
}
|
||||
65
src/onebit_lua_runtime.h
Normal file
65
src/onebit_lua_runtime.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#ifndef ONEBIT_LUA_RUNTIME_H
|
||||
#define ONEBIT_LUA_RUNTIME_H
|
||||
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
#include <godot_cpp/classes/ref_counted.hpp>
|
||||
#include <godot_cpp/core/binder_common.hpp>
|
||||
#include <godot_cpp/templates/hash_map.hpp>
|
||||
#include <godot_cpp/variant/array.hpp>
|
||||
#include <godot_cpp/variant/dictionary.hpp>
|
||||
#include <godot_cpp/variant/string.hpp>
|
||||
#include <godot_cpp/variant/variant.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <lua.h>
|
||||
}
|
||||
|
||||
using namespace godot;
|
||||
|
||||
class OneBitLuaRuntime : public RefCounted {
|
||||
GDCLASS(OneBitLuaRuntime, RefCounted)
|
||||
|
||||
private:
|
||||
lua_State *lua = nullptr;
|
||||
HashMap<String, int> 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
|
||||
BIN
src/onebit_lua_runtime.os
Normal file
BIN
src/onebit_lua_runtime.os
Normal file
Binary file not shown.
38
src/register_types.cpp
Normal file
38
src/register_types.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#include "register_types.h"
|
||||
|
||||
#include "onebit_lua_runtime.h"
|
||||
|
||||
#include <gdextension_interface.h>
|
||||
#include <godot_cpp/core/defs.hpp>
|
||||
#include <godot_cpp/godot.hpp>
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
11
src/register_types.h
Normal file
11
src/register_types.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef ONEBIT_LUA_REGISTER_TYPES_H
|
||||
#define ONEBIT_LUA_REGISTER_TYPES_H
|
||||
|
||||
#include <godot_cpp/core/class_db.hpp>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
void initialize_onebit_lua_module(ModuleInitializationLevel p_level);
|
||||
void uninitialize_onebit_lua_module(ModuleInitializationLevel p_level);
|
||||
|
||||
#endif
|
||||
BIN
src/register_types.os
Normal file
BIN
src/register_types.os
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue