Add godot-cpp submodule metadata
This commit is contained in:
parent
ac521356cc
commit
85bfb30b29
12 changed files with 516 additions and 121 deletions
116
.gitignore
vendored
116
.gitignore
vendored
|
|
@ -1,108 +1,26 @@
|
||||||
# ----------------- #
|
# Godot/editor cache
|
||||||
# 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/
|
.godot/
|
||||||
.claude
|
.godot-home/
|
||||||
.vscode
|
demo/.godot/
|
||||||
backup
|
|
||||||
|
|
||||||
# Ignore user-specific editor settings.
|
# SCons and native build output
|
||||||
# These files save the state of your editor (window layout, last open scenes, etc.)
|
.sconsign.dblite
|
||||||
# and should not be shared with the team.
|
*.os
|
||||||
.godot/editor/
|
*.o
|
||||||
editor_layout.cfg
|
*.a
|
||||||
command_system_guide.md
|
*.so
|
||||||
*.md
|
*.dll
|
||||||
|
*.dylib
|
||||||
|
*.framework/
|
||||||
|
demo/addons/onebit_lua/bin/
|
||||||
|
godot-cpp/bin/
|
||||||
|
godot-cpp/gen/
|
||||||
|
|
||||||
# ------------- #
|
# Editor/OS noise
|
||||||
# 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/
|
.vscode/
|
||||||
|
|
||||||
# JetBrains Rider / IntelliJ
|
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
# Visual Studio
|
|
||||||
.vs/
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------- #
|
|
||||||
# OS Specific #
|
|
||||||
# ---------------- #
|
|
||||||
|
|
||||||
# macOS
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
|
||||||
.LSOverride
|
|
||||||
|
|
||||||
# Windows
|
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
ehthumbs.db
|
|
||||||
Desktop.ini
|
|
||||||
|
|
||||||
|
# Logs
|
||||||
# ---------------- #
|
|
||||||
# Other Common #
|
|
||||||
# ---------------- #
|
|
||||||
|
|
||||||
# Log files
|
|
||||||
debug_log.txt
|
|
||||||
*.log
|
*.log
|
||||||
turn_system_plan.md
|
|
||||||
|
|
||||||
# Asset pack archives (you should unpack them and commit the assets, not the zip)
|
|
||||||
*.rar
|
|
||||||
*.7z
|
|
||||||
|
|
||||||
|
|
||||||
.CLAUDE.md
|
|
||||||
|
|
|
||||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
[submodule "godot-cpp"]
|
||||||
|
path = godot-cpp
|
||||||
|
url = https://github.com/godotengine/godot-cpp.git
|
||||||
|
branch = master
|
||||||
BIN
.sconsign.dblite
BIN
.sconsign.dblite
Binary file not shown.
102
README.md
Normal file
102
README.md
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
# OneBit Lua Runtime
|
||||||
|
|
||||||
|
Native GDExtension prototype for the 1BitRL v2 Lua runtime contract.
|
||||||
|
|
||||||
|
## Current Scope
|
||||||
|
|
||||||
|
This runtime is intentionally small and breaking:
|
||||||
|
|
||||||
|
- `api_version = 2` scripts only.
|
||||||
|
- No `game` table.
|
||||||
|
- Explicit `ctx` and `state` handler arguments.
|
||||||
|
- Lua emits commands and logs; it does not mutate the Godot world directly.
|
||||||
|
- Queries cross a Godot-owned query host boundary.
|
||||||
|
- Instruction budget is enabled for handler calls.
|
||||||
|
|
||||||
|
Initial Lua API:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
sense.is_walkable(x, y)
|
||||||
|
sense.find_nearest(group)
|
||||||
|
move.step(dx, dy)
|
||||||
|
log.say(message, duration)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Godot 4.6.
|
||||||
|
- `godot-cpp` master, targeting `api_version=4.6`.
|
||||||
|
- SCons.
|
||||||
|
- A Lua development package with `pkg-config`.
|
||||||
|
|
||||||
|
This machine currently uses Lua 5.1:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pkg-config lua-5.1 --cflags --libs
|
||||||
|
```
|
||||||
|
|
||||||
|
Lua 5.1 is explicit for now because it is the installed system package. If the project moves to Lua 5.4, install a Lua 5.4 development package and build with the matching pkg-config name, for example:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scons platform=linux target=template_debug api_version=4.6 lua_pkg=lua5.4
|
||||||
|
```
|
||||||
|
|
||||||
|
Script semantics differ between Lua 5.1 and 5.4, so do not change the runtime version silently.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Clone `godot-cpp` as a submodule or checkout at `godot-cpp/`, then run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
scons platform=linux target=template_debug api_version=4.6 -j4
|
||||||
|
```
|
||||||
|
|
||||||
|
The demo extension library is written to:
|
||||||
|
|
||||||
|
```text
|
||||||
|
demo/addons/onebit_lua/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Smoke Test
|
||||||
|
|
||||||
|
Run the demo project headlessly:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
godot --headless --path demo
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected behavior:
|
||||||
|
|
||||||
|
- Loads `OneBitLuaRuntime`.
|
||||||
|
- Loads a tiny `api_version = 2` script.
|
||||||
|
- Calls `on_update(ctx, state)`.
|
||||||
|
- Mutates `state.count`.
|
||||||
|
- Emits one `move` command.
|
||||||
|
- Emits one log entry.
|
||||||
|
- Calls the GDScript query host for `sense.is_walkable` and `sense.find_nearest`.
|
||||||
|
|
||||||
|
## Safe Lua Libraries
|
||||||
|
|
||||||
|
The runtime opens only selected Lua libraries:
|
||||||
|
|
||||||
|
- base
|
||||||
|
- math
|
||||||
|
- string
|
||||||
|
- table
|
||||||
|
|
||||||
|
It does not open:
|
||||||
|
|
||||||
|
- `io`
|
||||||
|
- `os`
|
||||||
|
- `package`
|
||||||
|
- `debug`
|
||||||
|
|
||||||
|
After opening the base library, the runtime removes dangerous dynamic-loading globals:
|
||||||
|
|
||||||
|
- `dofile`
|
||||||
|
- `load`
|
||||||
|
- `loadfile`
|
||||||
|
- `loadstring`
|
||||||
|
- `module`
|
||||||
|
- `require`
|
||||||
|
- `collectgarbage`
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
env = SConscript("godot-cpp/SConstruct")
|
env = SConscript("godot-cpp/SConstruct")
|
||||||
|
|
||||||
env.Append(CPPPATH=["src"])
|
env.Append(CPPPATH=["src"])
|
||||||
env.ParseConfig("pkg-config lua-5.1 --cflags --libs")
|
lua_pkg = ARGUMENTS.get("lua_pkg", "lua-5.1")
|
||||||
|
env.ParseConfig("pkg-config {} --cflags --libs".format(lua_pkg))
|
||||||
|
|
||||||
sources = Glob("src/*.cpp")
|
sources = Glob("src/*.cpp")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,12 @@ return {
|
||||||
|
|
||||||
on_update = function(ctx, state)
|
on_update = function(ctx, state)
|
||||||
state.count = (state.count or 0) + 1
|
state.count = (state.count or 0) + 1
|
||||||
|
state.world_x = ctx.self.world.x
|
||||||
|
state.tint_r = ctx.self.tint.r
|
||||||
if not ctx.self.is_moving and sense.is_walkable(2, 3) then
|
if not ctx.self.is_moving and sense.is_walkable(2, 3) then
|
||||||
local nearest = sense.find_nearest("players")
|
local nearest = sense.find_nearest("players")
|
||||||
move.step(1, 0)
|
move.step(1, 0)
|
||||||
log.say("tick", 1.0)
|
log.say("tick " .. tostring(state.count) .. " target " .. tostring(nearest.entity_id), 1.0)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +54,8 @@ return {
|
||||||
var ctx := {
|
var ctx := {
|
||||||
"self": {
|
"self": {
|
||||||
"is_moving": false,
|
"is_moving": false,
|
||||||
|
"world": Vector2(2.0, 3.0),
|
||||||
|
"tint": Color(0.25, 0.5, 0.75, 1.0),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var state := {}
|
var state := {}
|
||||||
|
|
@ -61,10 +65,78 @@ return {
|
||||||
get_tree().quit(1)
|
get_tree().quit(1)
|
||||||
return
|
return
|
||||||
|
|
||||||
if result.commands.size() != 1 or result.logs.size() != 1 or result.state.get("count", 0) != 1:
|
if result.commands.size() != 1 or result.logs.size() != 1 or result.state.get("count", 0) != 1 or result.state.get("world_x", 0) != 2 or result.state.get("tint_r", 0) != 0.25 or result.logs[0].message != "tick 1 target 7":
|
||||||
push_error("unexpected result: " + str(result))
|
push_error("unexpected result: " + str(result))
|
||||||
get_tree().quit(1)
|
get_tree().quit(1)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not _expect_runtime_error("""
|
||||||
|
return {
|
||||||
|
api_version = 2,
|
||||||
|
on_update = function(ctx, state)
|
||||||
|
sense.is_walkable = function() return true end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
""", "read-only"):
|
||||||
|
get_tree().quit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not _expect_runtime_error("""
|
||||||
|
return {
|
||||||
|
api_version = 2,
|
||||||
|
on_update = function(ctx, state)
|
||||||
|
sense = nil
|
||||||
|
end
|
||||||
|
}
|
||||||
|
""", "globals are read-only"):
|
||||||
|
get_tree().quit(1)
|
||||||
|
return
|
||||||
|
|
||||||
|
var mem_rt := OneBitLuaRuntime.new()
|
||||||
|
mem_rt.set_entity_id(42)
|
||||||
|
mem_rt.set_query_host(query_host)
|
||||||
|
mem_rt.set_instruction_budget(1000000)
|
||||||
|
mem_rt.set_memory_budget(1024 * 1024)
|
||||||
|
var mem_loaded := mem_rt.load_string("""
|
||||||
|
return {
|
||||||
|
api_version = 2,
|
||||||
|
on_update = function(ctx, state)
|
||||||
|
local chunks = {}
|
||||||
|
for i = 1, 10000 do
|
||||||
|
chunks[i] = string.rep("x", 4000) .. tostring(i)
|
||||||
|
end
|
||||||
|
state.done = true
|
||||||
|
end
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
if not mem_loaded.ok:
|
||||||
|
push_error("memory test failed to load: " + str(mem_loaded.error))
|
||||||
|
get_tree().quit(1)
|
||||||
|
return
|
||||||
|
var mem_result := mem_rt.call_event("on_update", ctx, {}, [])
|
||||||
|
if mem_result.ok or not str(mem_result.error).contains("memory budget"):
|
||||||
|
push_error("memory budget test did not fail as expected: " + str(mem_result))
|
||||||
|
get_tree().quit(1)
|
||||||
|
return
|
||||||
|
|
||||||
print("OneBitLuaRuntime smoke test passed: ", result)
|
print("OneBitLuaRuntime smoke test passed: ", result)
|
||||||
get_tree().quit(0)
|
get_tree().quit(0)
|
||||||
|
|
||||||
|
|
||||||
|
func _expect_runtime_error(source: String, expected_error: String) -> bool:
|
||||||
|
var rt := OneBitLuaRuntime.new()
|
||||||
|
rt.set_entity_id(42)
|
||||||
|
rt.set_query_host(query_host)
|
||||||
|
var loaded := rt.load_string(source)
|
||||||
|
if not loaded.ok:
|
||||||
|
push_error("expected load success, got: " + str(loaded.error))
|
||||||
|
return false
|
||||||
|
|
||||||
|
var result := rt.call_event("on_update", {"self": {"is_moving": false}}, {}, [])
|
||||||
|
if result.ok:
|
||||||
|
push_error("expected runtime error, got success: " + str(result))
|
||||||
|
return false
|
||||||
|
if not str(result.error).contains(expected_error) or not str(result.error).contains("[string \"<string>\"]"):
|
||||||
|
push_error("unexpected runtime error: " + str(result.error))
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,13 @@
|
||||||
#include <godot_cpp/classes/file_access.hpp>
|
#include <godot_cpp/classes/file_access.hpp>
|
||||||
#include <godot_cpp/core/class_db.hpp>
|
#include <godot_cpp/core/class_db.hpp>
|
||||||
#include <godot_cpp/core/object.hpp>
|
#include <godot_cpp/core/object.hpp>
|
||||||
|
#include <godot_cpp/variant/color.hpp>
|
||||||
#include <godot_cpp/variant/utility_functions.hpp>
|
#include <godot_cpp/variant/utility_functions.hpp>
|
||||||
|
#include <godot_cpp/variant/vector2.hpp>
|
||||||
#include <godot_cpp/variant/vector2i.hpp>
|
#include <godot_cpp/variant/vector2i.hpp>
|
||||||
|
#include <godot_cpp/variant/vector3.hpp>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <lauxlib.h>
|
#include <lauxlib.h>
|
||||||
|
|
@ -15,6 +20,8 @@ extern "C" {
|
||||||
#define LUA_OK 0
|
#define LUA_OK 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static const char *ONEBIT_RUNTIME_REGISTRY_KEY = "onebit_lua_runtime";
|
||||||
|
|
||||||
using namespace godot;
|
using namespace godot;
|
||||||
|
|
||||||
void OneBitLuaRuntime::_bind_methods() {
|
void OneBitLuaRuntime::_bind_methods() {
|
||||||
|
|
@ -24,6 +31,7 @@ void OneBitLuaRuntime::_bind_methods() {
|
||||||
ClassDB::bind_method(D_METHOD("set_entity_id", "entity_id"), &OneBitLuaRuntime::set_entity_id);
|
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_query_host", "host"), &OneBitLuaRuntime::set_query_host);
|
||||||
ClassDB::bind_method(D_METHOD("set_instruction_budget", "budget"), &OneBitLuaRuntime::set_instruction_budget);
|
ClassDB::bind_method(D_METHOD("set_instruction_budget", "budget"), &OneBitLuaRuntime::set_instruction_budget);
|
||||||
|
ClassDB::bind_method(D_METHOD("set_memory_budget", "bytes"), &OneBitLuaRuntime::set_memory_budget);
|
||||||
}
|
}
|
||||||
|
|
||||||
OneBitLuaRuntime::OneBitLuaRuntime() {
|
OneBitLuaRuntime::OneBitLuaRuntime() {
|
||||||
|
|
@ -31,21 +39,69 @@ OneBitLuaRuntime::OneBitLuaRuntime() {
|
||||||
}
|
}
|
||||||
|
|
||||||
OneBitLuaRuntime::~OneBitLuaRuntime() {
|
OneBitLuaRuntime::~OneBitLuaRuntime() {
|
||||||
_reset_lua();
|
_close_lua();
|
||||||
}
|
}
|
||||||
|
|
||||||
void OneBitLuaRuntime::_reset_lua() {
|
void OneBitLuaRuntime::_close_lua() {
|
||||||
if (lua) {
|
if (lua) {
|
||||||
_clear_handlers();
|
_clear_handlers();
|
||||||
lua_close(lua);
|
lua_close(lua);
|
||||||
lua = nullptr;
|
lua = nullptr;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lua = luaL_newstate();
|
void OneBitLuaRuntime::_reset_lua() {
|
||||||
|
_close_lua();
|
||||||
|
|
||||||
|
memory_used_bytes = 0;
|
||||||
instruction_budget_exceeded = false;
|
instruction_budget_exceeded = false;
|
||||||
|
memory_budget_exceeded = false;
|
||||||
|
lua = lua_newstate(&OneBitLuaRuntime::_lua_allocator, this);
|
||||||
|
if (!lua) {
|
||||||
|
memory_budget_exceeded = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_open_safe_libraries();
|
||||||
_install_api();
|
_install_api();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OneBitLuaRuntime::_open_safe_libraries() {
|
||||||
|
struct SafeLib {
|
||||||
|
const char *name;
|
||||||
|
lua_CFunction open;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SafeLib safe_libs[] = {
|
||||||
|
{ "", luaopen_base },
|
||||||
|
{ LUA_MATHLIBNAME, luaopen_math },
|
||||||
|
{ LUA_STRLIBNAME, luaopen_string },
|
||||||
|
{ LUA_TABLIBNAME, luaopen_table },
|
||||||
|
{ nullptr, nullptr },
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const SafeLib *lib = safe_libs; lib->open; lib++) {
|
||||||
|
lua_pushcfunction(lua, lib->open);
|
||||||
|
lua_pushstring(lua, lib->name);
|
||||||
|
lua_call(lua, 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *blocked_globals[] = {
|
||||||
|
"dofile",
|
||||||
|
"load",
|
||||||
|
"loadfile",
|
||||||
|
"loadstring",
|
||||||
|
"module",
|
||||||
|
"require",
|
||||||
|
"collectgarbage",
|
||||||
|
nullptr,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const char **name = blocked_globals; *name; name++) {
|
||||||
|
lua_pushnil(lua);
|
||||||
|
lua_setglobal(lua, *name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void OneBitLuaRuntime::_clear_handlers() {
|
void OneBitLuaRuntime::_clear_handlers() {
|
||||||
if (!lua) {
|
if (!lua) {
|
||||||
handlers.clear();
|
handlers.clear();
|
||||||
|
|
@ -60,7 +116,7 @@ void OneBitLuaRuntime::_clear_handlers() {
|
||||||
|
|
||||||
void OneBitLuaRuntime::_install_api() {
|
void OneBitLuaRuntime::_install_api() {
|
||||||
lua_pushlightuserdata(lua, this);
|
lua_pushlightuserdata(lua, this);
|
||||||
lua_setglobal(lua, "__onebit_runtime");
|
lua_setfield(lua, LUA_REGISTRYINDEX, ONEBIT_RUNTIME_REGISTRY_KEY);
|
||||||
|
|
||||||
lua_newtable(lua);
|
lua_newtable(lua);
|
||||||
lua_pushlightuserdata(lua, this);
|
lua_pushlightuserdata(lua, this);
|
||||||
|
|
@ -69,19 +125,52 @@ void OneBitLuaRuntime::_install_api() {
|
||||||
lua_pushlightuserdata(lua, this);
|
lua_pushlightuserdata(lua, this);
|
||||||
lua_pushcclosure(lua, &OneBitLuaRuntime::_lua_sense_find_nearest, 1);
|
lua_pushcclosure(lua, &OneBitLuaRuntime::_lua_sense_find_nearest, 1);
|
||||||
lua_setfield(lua, -2, "find_nearest");
|
lua_setfield(lua, -2, "find_nearest");
|
||||||
lua_setglobal(lua, "sense");
|
_set_readonly_global("sense");
|
||||||
|
|
||||||
lua_newtable(lua);
|
lua_newtable(lua);
|
||||||
lua_pushlightuserdata(lua, this);
|
lua_pushlightuserdata(lua, this);
|
||||||
lua_pushcclosure(lua, &OneBitLuaRuntime::_lua_move_step, 1);
|
lua_pushcclosure(lua, &OneBitLuaRuntime::_lua_move_step, 1);
|
||||||
lua_setfield(lua, -2, "step");
|
lua_setfield(lua, -2, "step");
|
||||||
lua_setglobal(lua, "move");
|
_set_readonly_global("move");
|
||||||
|
|
||||||
lua_newtable(lua);
|
lua_newtable(lua);
|
||||||
lua_pushlightuserdata(lua, this);
|
lua_pushlightuserdata(lua, this);
|
||||||
lua_pushcclosure(lua, &OneBitLuaRuntime::_lua_log_say, 1);
|
lua_pushcclosure(lua, &OneBitLuaRuntime::_lua_log_say, 1);
|
||||||
lua_setfield(lua, -2, "say");
|
lua_setfield(lua, -2, "say");
|
||||||
lua_setglobal(lua, "log");
|
_set_readonly_global("log");
|
||||||
|
}
|
||||||
|
|
||||||
|
void OneBitLuaRuntime::_set_readonly_global(const char *name) {
|
||||||
|
int api_table = lua_gettop(lua);
|
||||||
|
|
||||||
|
lua_newtable(lua);
|
||||||
|
int proxy_table = lua_gettop(lua);
|
||||||
|
|
||||||
|
lua_newtable(lua);
|
||||||
|
lua_pushvalue(lua, api_table);
|
||||||
|
lua_setfield(lua, -2, "__index");
|
||||||
|
lua_pushcfunction(lua, &OneBitLuaRuntime::_lua_readonly_error);
|
||||||
|
lua_setfield(lua, -2, "__newindex");
|
||||||
|
lua_pushboolean(lua, 0);
|
||||||
|
lua_setfield(lua, -2, "__metatable");
|
||||||
|
lua_setmetatable(lua, proxy_table);
|
||||||
|
|
||||||
|
lua_setglobal(lua, name);
|
||||||
|
lua_pop(lua, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OneBitLuaRuntime::_push_script_environment() {
|
||||||
|
lua_newtable(lua);
|
||||||
|
int env_table = lua_gettop(lua);
|
||||||
|
|
||||||
|
lua_newtable(lua);
|
||||||
|
lua_pushvalue(lua, LUA_GLOBALSINDEX);
|
||||||
|
lua_setfield(lua, -2, "__index");
|
||||||
|
lua_pushcfunction(lua, &OneBitLuaRuntime::_lua_global_write_error);
|
||||||
|
lua_setfield(lua, -2, "__newindex");
|
||||||
|
lua_pushboolean(lua, 0);
|
||||||
|
lua_setfield(lua, -2, "__metatable");
|
||||||
|
lua_setmetatable(lua, env_table);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary OneBitLuaRuntime::_base_result(bool ok, const String &error) const {
|
Dictionary OneBitLuaRuntime::_base_result(bool ok, const String &error) const {
|
||||||
|
|
@ -99,23 +188,51 @@ Dictionary OneBitLuaRuntime::_error_result(const String &error) const {
|
||||||
return _base_result(false, error);
|
return _base_result(false, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String OneBitLuaRuntime::_pop_lua_error() {
|
||||||
|
String error;
|
||||||
|
if (lua && lua_gettop(lua) > 0 && lua_isstring(lua, -1)) {
|
||||||
|
error = String(lua_tostring(lua, -1));
|
||||||
|
} else {
|
||||||
|
error = "Unknown Lua error";
|
||||||
|
}
|
||||||
|
if (lua && lua_gettop(lua) > 0) {
|
||||||
|
lua_pop(lua, 1);
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
Dictionary OneBitLuaRuntime::load_string(const String &source) {
|
Dictionary OneBitLuaRuntime::load_string(const String &source) {
|
||||||
|
return _load_source(source, "<string>");
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary OneBitLuaRuntime::_load_source(const String &source, const String &source_name) {
|
||||||
_reset_lua();
|
_reset_lua();
|
||||||
commands.clear();
|
commands.clear();
|
||||||
logs.clear();
|
logs.clear();
|
||||||
|
|
||||||
CharString utf8 = source.utf8();
|
if (!lua) {
|
||||||
if (luaL_loadstring(lua, utf8.get_data()) != LUA_OK) {
|
return _error_result("Failed to create Lua state");
|
||||||
String error = String(lua_tostring(lua, -1));
|
|
||||||
lua_pop(lua, 1);
|
|
||||||
return _error_result(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lua_pcall(lua, 0, 1, 0) != LUA_OK) {
|
CharString utf8 = source.utf8();
|
||||||
String error = String(lua_tostring(lua, -1));
|
CharString source_name_utf8 = source_name.utf8();
|
||||||
lua_pop(lua, 1);
|
if (luaL_loadbuffer(lua, utf8.get_data(), utf8.length(), source_name_utf8.get_data()) != LUA_OK) {
|
||||||
|
return _error_result(_pop_lua_error());
|
||||||
|
}
|
||||||
|
|
||||||
|
_push_script_environment();
|
||||||
|
lua_setfenv(lua, -2);
|
||||||
|
|
||||||
|
lua_pushcfunction(lua, &OneBitLuaRuntime::_traceback_handler);
|
||||||
|
lua_insert(lua, -2);
|
||||||
|
int error_handler = lua_gettop(lua) - 1;
|
||||||
|
|
||||||
|
if (lua_pcall(lua, 0, 1, error_handler) != LUA_OK) {
|
||||||
|
String error = _pop_lua_error();
|
||||||
|
lua_remove(lua, error_handler);
|
||||||
return _error_result(error);
|
return _error_result(error);
|
||||||
}
|
}
|
||||||
|
lua_remove(lua, error_handler);
|
||||||
|
|
||||||
if (!lua_istable(lua, -1)) {
|
if (!lua_istable(lua, -1)) {
|
||||||
lua_pop(lua, 1);
|
lua_pop(lua, 1);
|
||||||
|
|
@ -134,7 +251,7 @@ Dictionary OneBitLuaRuntime::load_script(const String &path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
String source = file->get_as_text();
|
String source = file->get_as_text();
|
||||||
return load_string(source);
|
return _load_source(source, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary OneBitLuaRuntime::_extract_handlers(int table_index, const String &source_name) {
|
Dictionary OneBitLuaRuntime::_extract_handlers(int table_index, const String &source_name) {
|
||||||
|
|
@ -191,16 +308,27 @@ Dictionary OneBitLuaRuntime::call_event(const String &event_name, const Dictiona
|
||||||
_push_variant(args[i]);
|
_push_variant(args[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int function_index = lua_gettop(lua) - (2 + int(args.size()));
|
||||||
|
lua_pushcfunction(lua, &OneBitLuaRuntime::_traceback_handler);
|
||||||
|
lua_insert(lua, function_index);
|
||||||
|
int error_handler = function_index;
|
||||||
|
|
||||||
lua_sethook(lua, &OneBitLuaRuntime::_instruction_hook, LUA_MASKCOUNT, int(instruction_budget));
|
lua_sethook(lua, &OneBitLuaRuntime::_instruction_hook, LUA_MASKCOUNT, int(instruction_budget));
|
||||||
int status = lua_pcall(lua, 2 + int(args.size()), 0, 0);
|
int status = lua_pcall(lua, 2 + int(args.size()), 0, 0);
|
||||||
lua_sethook(lua, nullptr, 0, 0);
|
lua_sethook(lua, nullptr, 0, 0);
|
||||||
|
|
||||||
if (status != LUA_OK) {
|
if (status != LUA_OK) {
|
||||||
String error = instruction_budget_exceeded ? String("Lua instruction budget exceeded") : String(lua_tostring(lua, -1));
|
String error = _pop_lua_error();
|
||||||
lua_pop(lua, 1);
|
if (instruction_budget_exceeded) {
|
||||||
|
error = "Lua instruction budget exceeded\n" + error;
|
||||||
|
} else if (memory_budget_exceeded) {
|
||||||
|
error = "Lua memory budget exceeded\n" + error;
|
||||||
|
}
|
||||||
|
lua_remove(lua, error_handler);
|
||||||
luaL_unref(lua, LUA_REGISTRYINDEX, state_ref);
|
luaL_unref(lua, LUA_REGISTRYINDEX, state_ref);
|
||||||
return _error_result(error);
|
return _error_result(error);
|
||||||
}
|
}
|
||||||
|
lua_remove(lua, error_handler);
|
||||||
|
|
||||||
lua_rawgeti(lua, LUA_REGISTRYINDEX, state_ref);
|
lua_rawgeti(lua, LUA_REGISTRYINDEX, state_ref);
|
||||||
Variant updated_state = _read_lua_value(-1);
|
Variant updated_state = _read_lua_value(-1);
|
||||||
|
|
@ -226,10 +354,74 @@ void OneBitLuaRuntime::set_instruction_budget(int64_t p_budget) {
|
||||||
instruction_budget = p_budget > 0 ? p_budget : 1;
|
instruction_budget = p_budget > 0 ? p_budget : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OneBitLuaRuntime::set_memory_budget(int64_t p_bytes) {
|
||||||
|
memory_budget_bytes = p_bytes > 0 ? p_bytes : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *OneBitLuaRuntime::_lua_allocator(void *ud, void *ptr, size_t osize, size_t nsize) {
|
||||||
|
(void)osize;
|
||||||
|
OneBitLuaRuntime *runtime = static_cast<OneBitLuaRuntime *>(ud);
|
||||||
|
const size_t header_size = sizeof(size_t);
|
||||||
|
|
||||||
|
if (nsize == 0) {
|
||||||
|
if (runtime) {
|
||||||
|
if (ptr) {
|
||||||
|
void *base_ptr = static_cast<char *>(ptr) - header_size;
|
||||||
|
size_t old_size = *static_cast<size_t *>(base_ptr);
|
||||||
|
runtime->memory_used_bytes -= int64_t(old_size);
|
||||||
|
std::free(base_ptr);
|
||||||
|
}
|
||||||
|
if (runtime->memory_used_bytes < 0) {
|
||||||
|
runtime->memory_used_bytes = 0;
|
||||||
|
}
|
||||||
|
} else if (ptr) {
|
||||||
|
std::free(static_cast<char *>(ptr) - header_size);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!runtime) {
|
||||||
|
void *base_ptr = ptr ? static_cast<char *>(ptr) - header_size : nullptr;
|
||||||
|
void *new_base = std::realloc(base_ptr, nsize + header_size);
|
||||||
|
if (!new_base) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
*static_cast<size_t *>(new_base) = nsize;
|
||||||
|
return static_cast<char *>(new_base) + header_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t old_raw_size = 0;
|
||||||
|
void *base_ptr = nullptr;
|
||||||
|
if (ptr) {
|
||||||
|
base_ptr = static_cast<char *>(ptr) - header_size;
|
||||||
|
old_raw_size = *static_cast<size_t *>(base_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t old_size = int64_t(old_raw_size);
|
||||||
|
int64_t new_size = int64_t(nsize);
|
||||||
|
int64_t delta = new_size - old_size;
|
||||||
|
if (delta > 0 && runtime->memory_used_bytes + delta > runtime->memory_budget_bytes) {
|
||||||
|
runtime->memory_budget_exceeded = true;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *new_base = std::realloc(base_ptr, nsize + header_size);
|
||||||
|
if (!new_base) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
*static_cast<size_t *>(new_base) = nsize;
|
||||||
|
|
||||||
|
runtime->memory_used_bytes += delta;
|
||||||
|
if (runtime->memory_used_bytes < 0) {
|
||||||
|
runtime->memory_used_bytes = 0;
|
||||||
|
}
|
||||||
|
return static_cast<char *>(new_base) + header_size;
|
||||||
|
}
|
||||||
|
|
||||||
void OneBitLuaRuntime::_instruction_hook(lua_State *L, lua_Debug *debug) {
|
void OneBitLuaRuntime::_instruction_hook(lua_State *L, lua_Debug *debug) {
|
||||||
(void)debug;
|
(void)debug;
|
||||||
OneBitLuaRuntime *runtime = nullptr;
|
OneBitLuaRuntime *runtime = nullptr;
|
||||||
lua_getglobal(L, "__onebit_runtime");
|
lua_getfield(L, LUA_REGISTRYINDEX, ONEBIT_RUNTIME_REGISTRY_KEY);
|
||||||
if (lua_islightuserdata(L, -1)) {
|
if (lua_islightuserdata(L, -1)) {
|
||||||
runtime = static_cast<OneBitLuaRuntime *>(lua_touserdata(L, -1));
|
runtime = static_cast<OneBitLuaRuntime *>(lua_touserdata(L, -1));
|
||||||
}
|
}
|
||||||
|
|
@ -241,6 +433,51 @@ void OneBitLuaRuntime::_instruction_hook(lua_State *L, lua_Debug *debug) {
|
||||||
luaL_error(L, "Lua instruction budget exceeded");
|
luaL_error(L, "Lua instruction budget exceeded");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int OneBitLuaRuntime::_lua_readonly_error(lua_State *L) {
|
||||||
|
const char *key = lua_tostring(L, 2);
|
||||||
|
if (!key) {
|
||||||
|
key = "<non-string key>";
|
||||||
|
}
|
||||||
|
return luaL_error(L, "Lua API tables are read-only: cannot assign '%s'", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
int OneBitLuaRuntime::_lua_global_write_error(lua_State *L) {
|
||||||
|
const char *key = lua_tostring(L, 2);
|
||||||
|
if (!key) {
|
||||||
|
key = "<non-string key>";
|
||||||
|
}
|
||||||
|
return luaL_error(L, "Lua globals are read-only: attempted to assign '%s'", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
int OneBitLuaRuntime::_traceback_handler(lua_State *L) {
|
||||||
|
const char *message = lua_tostring(L, 1);
|
||||||
|
if (!message) {
|
||||||
|
message = "Unknown Lua error";
|
||||||
|
}
|
||||||
|
|
||||||
|
luaL_Buffer buffer;
|
||||||
|
luaL_buffinit(L, &buffer);
|
||||||
|
luaL_addstring(&buffer, message);
|
||||||
|
luaL_addstring(&buffer, "\nstack traceback:");
|
||||||
|
|
||||||
|
lua_Debug ar;
|
||||||
|
int level = 1;
|
||||||
|
while (lua_getstack(L, level, &ar)) {
|
||||||
|
lua_getinfo(L, "Sln", &ar);
|
||||||
|
luaL_addstring(&buffer, "\n ");
|
||||||
|
luaL_addstring(&buffer, ar.short_src ? ar.short_src : "<unknown>");
|
||||||
|
luaL_addstring(&buffer, ":");
|
||||||
|
CharString line = String::num_int64(ar.currentline).utf8();
|
||||||
|
luaL_addstring(&buffer, line.get_data());
|
||||||
|
luaL_addstring(&buffer, ": in ");
|
||||||
|
luaL_addstring(&buffer, ar.name ? ar.name : "?");
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
|
||||||
|
luaL_pushresult(&buffer);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
Variant OneBitLuaRuntime::_query(const String &query_name, const Dictionary ¶ms) {
|
Variant OneBitLuaRuntime::_query(const String &query_name, const Dictionary ¶ms) {
|
||||||
if (query_host_id == 0) {
|
if (query_host_id == 0) {
|
||||||
return Variant();
|
return Variant();
|
||||||
|
|
@ -329,6 +566,15 @@ bool OneBitLuaRuntime::_push_variant(const Variant &value) {
|
||||||
lua_pushstring(lua, utf8.get_data());
|
lua_pushstring(lua, utf8.get_data());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case Variant::VECTOR2: {
|
||||||
|
Vector2 vector = value;
|
||||||
|
lua_newtable(lua);
|
||||||
|
lua_pushnumber(lua, vector.x);
|
||||||
|
lua_setfield(lua, -2, "x");
|
||||||
|
lua_pushnumber(lua, vector.y);
|
||||||
|
lua_setfield(lua, -2, "y");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case Variant::VECTOR2I: {
|
case Variant::VECTOR2I: {
|
||||||
Vector2i vector = value;
|
Vector2i vector = value;
|
||||||
lua_newtable(lua);
|
lua_newtable(lua);
|
||||||
|
|
@ -338,6 +584,30 @@ bool OneBitLuaRuntime::_push_variant(const Variant &value) {
|
||||||
lua_setfield(lua, -2, "y");
|
lua_setfield(lua, -2, "y");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
case Variant::VECTOR3: {
|
||||||
|
Vector3 vector = value;
|
||||||
|
lua_newtable(lua);
|
||||||
|
lua_pushnumber(lua, vector.x);
|
||||||
|
lua_setfield(lua, -2, "x");
|
||||||
|
lua_pushnumber(lua, vector.y);
|
||||||
|
lua_setfield(lua, -2, "y");
|
||||||
|
lua_pushnumber(lua, vector.z);
|
||||||
|
lua_setfield(lua, -2, "z");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case Variant::COLOR: {
|
||||||
|
Color color = value;
|
||||||
|
lua_newtable(lua);
|
||||||
|
lua_pushnumber(lua, color.r);
|
||||||
|
lua_setfield(lua, -2, "r");
|
||||||
|
lua_pushnumber(lua, color.g);
|
||||||
|
lua_setfield(lua, -2, "g");
|
||||||
|
lua_pushnumber(lua, color.b);
|
||||||
|
lua_setfield(lua, -2, "b");
|
||||||
|
lua_pushnumber(lua, color.a);
|
||||||
|
lua_setfield(lua, -2, "a");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case Variant::DICTIONARY: {
|
case Variant::DICTIONARY: {
|
||||||
Dictionary dict = value;
|
Dictionary dict = value;
|
||||||
lua_newtable(lua);
|
lua_newtable(lua);
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,10 @@ private:
|
||||||
int64_t entity_id = 0;
|
int64_t entity_id = 0;
|
||||||
uint64_t query_host_id = 0;
|
uint64_t query_host_id = 0;
|
||||||
int64_t instruction_budget = 100000;
|
int64_t instruction_budget = 100000;
|
||||||
|
int64_t memory_budget_bytes = 16 * 1024 * 1024;
|
||||||
|
int64_t memory_used_bytes = 0;
|
||||||
bool instruction_budget_exceeded = false;
|
bool instruction_budget_exceeded = false;
|
||||||
|
bool memory_budget_exceeded = false;
|
||||||
|
|
||||||
static void _bind_methods();
|
static void _bind_methods();
|
||||||
|
|
||||||
|
|
@ -35,11 +38,21 @@ private:
|
||||||
static int _lua_log_say(lua_State *L);
|
static int _lua_log_say(lua_State *L);
|
||||||
static int _lua_sense_is_walkable(lua_State *L);
|
static int _lua_sense_is_walkable(lua_State *L);
|
||||||
static int _lua_sense_find_nearest(lua_State *L);
|
static int _lua_sense_find_nearest(lua_State *L);
|
||||||
|
static int _lua_readonly_error(lua_State *L);
|
||||||
|
static int _lua_global_write_error(lua_State *L);
|
||||||
|
static int _traceback_handler(lua_State *L);
|
||||||
static void _instruction_hook(lua_State *L, lua_Debug *debug);
|
static void _instruction_hook(lua_State *L, lua_Debug *debug);
|
||||||
|
static void *_lua_allocator(void *ud, void *ptr, size_t osize, size_t nsize);
|
||||||
|
|
||||||
|
void _close_lua();
|
||||||
void _reset_lua();
|
void _reset_lua();
|
||||||
|
void _open_safe_libraries();
|
||||||
void _install_api();
|
void _install_api();
|
||||||
|
void _set_readonly_global(const char *name);
|
||||||
|
void _push_script_environment();
|
||||||
void _clear_handlers();
|
void _clear_handlers();
|
||||||
|
Dictionary _load_source(const String &source, const String &source_name);
|
||||||
|
String _pop_lua_error();
|
||||||
Dictionary _base_result(bool ok, const String &error = "") const;
|
Dictionary _base_result(bool ok, const String &error = "") const;
|
||||||
Dictionary _error_result(const String &error) const;
|
Dictionary _error_result(const String &error) const;
|
||||||
Variant _query(const String &query_name, const Dictionary ¶ms);
|
Variant _query(const String &query_name, const Dictionary ¶ms);
|
||||||
|
|
@ -60,6 +73,7 @@ public:
|
||||||
void set_entity_id(int64_t p_entity_id);
|
void set_entity_id(int64_t p_entity_id);
|
||||||
void set_query_host(Object *p_host);
|
void set_query_host(Object *p_host);
|
||||||
void set_instruction_budget(int64_t p_budget);
|
void set_instruction_budget(int64_t p_budget);
|
||||||
|
void set_memory_budget(int64_t p_bytes);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -30,6 +30,7 @@ GDExtensionBool GDE_EXPORT onebit_lua_library_init(
|
||||||
godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, 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_initializer(initialize_onebit_lua_module);
|
||||||
|
|
||||||
init_obj.register_terminator(uninitialize_onebit_lua_module);
|
init_obj.register_terminator(uninitialize_onebit_lua_module);
|
||||||
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
|
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
13
src/samq2.code-workspace
Normal file
13
src/samq2.code-workspace
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "../../samq2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"git.ignoreLimitWarning": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue