Compare commits

...

9 Commits

8 changed files with 280 additions and 9 deletions

23
cmake/imgui4lua.cmake Normal file

@ -0,0 +1,23 @@
function(_imgui4lua_main)
set(dir "${CMAKE_CURRENT_BINARY_DIR}/generated")
set(py "${PROJECT_SOURCE_DIR}/cmake/imgui4lua.py")
set(src "${imgui_SOURCE_DIR}/imgui.h")
set(dst "${dir}/imgui4lua.inc")
find_program(SH sh REQUIRED)
find_program(PYTHON3 python3 REQUIRED)
find_program(CLANGXX clang++ REQUIRED)
make_directory("${dir}")
add_custom_command(
COMMAND ${SH} -c "${PYTHON3} '${py}' '${src}' > '${dst}'"
OUTPUT "${dst}"
DEPENDS "${src}" "${py}"
VERBATIM
)
add_library(imgui4lua INTERFACE)
target_sources(imgui4lua PUBLIC "${dst}")
target_include_directories(imgui4lua INTERFACE ${CMAKE_CURRENT_BINARY_DIR})
endfunction()
_imgui4lua_main()

208
cmake/imgui4lua.py Executable file

@ -0,0 +1,208 @@
import json
import subprocess
import sys
# ---- GENERATOR DEFINITIONS
def gen_enum(item):
name = item.get("name", "")
if name not in kEnumWhitelist:
return
short_name = name[5:-1]
print(f"{{ // {name}")
for child in item.get("inner", []):
member_name = child["name"]
if not member_name.startswith(name): continue
member_short_name = member_name[len(name):]
print(f" lua_pushinteger(L, static_cast<lua_Integer>({member_name})); "+
f"lua_setfield(L, -2, \"{short_name}_{member_short_name}\");")
print(f"}} // {name}")
print()
def gen_func(name, ovlds):
ovld_root = (0, {}, None)
for ovld in ovlds:
ovld_node = ovld_root
idx = 0
for arg in ovld.get("inner", []):
if "ParmVarDecl" != arg.get("kind"): continue
tname = get_type_from_argument(arg)
if tname not in kLuaTypeChecker:
break
ovld_node[1][tname] = (idx, {}, None)
ovld_node = ovld_node[1][tname]
idx += 1
ovld_node[1]["$"] = (idx, {}, ovld)
def _output(ovld_node, depth=1):
indent = " "*depth
if 0 == len(ovld_node[1]):
gen_single_func(ovld_node[2], indent=indent)
elif 1 == len(ovld_node[1]):
_output(*ovld_node[1].values(), depth=depth)
else:
for tname in ovld_node[1]:
checker = kLuaTypeChecker[tname].replace("#", str(ovld_node[0]+1))
print(f"{indent}if ({checker}) {{")
_output(ovld_node[1][tname], depth=depth+1)
print(f"{indent}}}")
print(f"{indent}return luaL_error(L, \"unexpected type in param#{ovld_node[0]}\");")
print("lua_pushcfunction(L, [](auto L) {")
print(" (void) L;")
_output(ovld_root)
print("});")
print(f"lua_setfield(L, -2, \"{name}\");")
print()
def gen_single_func(item, indent=""):
name = item["name"]
pops = []
params = []
pcount = 0
for arg in item.get("inner", []):
if "ParmVarDecl" != arg.get("kind"): continue
pop, param, push = gen_argument(pcount, arg)
pcount += 1
pops .extend(pop)
params.extend(param)
push .extend(push)
pushes = gen_return(item)
use_ret = 0 < len(pushes)
# text output
nl = "\n"+indent
cm = ", "
if 0 < len(pops):
print(f"{indent}{nl.join(pops)}")
if use_ret:
print(f"{indent}const auto r = ImGui::{name}({cm.join(params)});")
else:
print(f"{indent}ImGui::{name}({cm.join(params)});")
if 0 < len(pushes):
print(f"{indent}{nl.join(pushes)}")
print(f"{indent}return {len(pushes)};")
def gen_return(item):
ftype = item["type"]["qualType"]
type = ftype[0:ftype.find("(")-1]
if "void" == type:
return []
if "bool" == type:
return ["lua_pushboolean(L, r);"]
if "float" == type:
return ["lua_pushnumber(L, static_cast<lua_Number>(r));"]
if "ImVec2" == type:
return [
"lua_pushnumber(L, static_cast<lua_Number>(r.x));",
"lua_pushnumber(L, static_cast<lua_Number>(r.y));",
]
print(f"unknown return type: {type}", file=sys.stderr)
return []
def gen_argument(pc, item):
type = item["type"].get("desugaredQualType", item["type"]["qualType"])
n = pc+1
pn = f"p{pc}"
if type in ["int", "unsigned int"]:
return ([f"const int {pn} = static_cast<{type}>(luaL_checkinteger(L, {n}));"], [pn], [])
if "bool" == type:
return ([f"const bool {pn} = lua_toboolean(L, {n});"], [pn], [])
if "const char *" == type:
return ([f"const char* {pn} = luaL_checkstring(L, {n});"], [pn], [])
if "const ImVec2 &" == type:
return ([
f"const float {pn}_1 = static_cast<float>(luaL_checknumber(L, {n}));",
f"const float {pn}_2 = static_cast<float>(luaL_checknumber(L, {n}));",
], [f"ImVec2 {{{pn}_1, {pn}_2}}"], [])
if "bool *" == type:
return ([f"bool {pn};"], [f"&{pn}"], [f"lua_pushboolean(L, {pn});"])
print(f"unknown argument type: {type}", file=sys.stderr)
return ([], [], [])
# ---- GENERATOR UTILITIES
def get_type_from_argument(item):
return item["type"].get("desugaredQualType", item["type"]["qualType"])
# ---- WALKER DEFINITIONS
class Walker:
def __init__(self):
self._funcs = {}
self._enums = {}
def emit(self):
for x in self._enums: gen_enum(self._enums[x])
for x in self._funcs: gen_func(x, self._funcs[x])
def walk(self, item):
kind = item.get("kind")
name = item.get("name")
if "EnumDecl" == kind:
if name in kEnumWhitelist:
self._enums[name] = item
else:
w = self.walk
if "NamespaceDecl" == kind:
w = self._walk_ns
for child in item.get("inner", []):
w(child)
def _walk_ns(self, item):
kind = item.get("kind")
name = item.get("name")
if "FunctionDecl" == kind:
if name in kFuncWhitelist:
if name not in self._funcs:
self._funcs[name] = [item]
else:
self._funcs[name].append(item)
else:
self.walk(item)
# ---- DATA DEFINITIONS
kFuncWhitelist = [
"Begin",
"End",
"BeginChild",
"EndChild",
"IsWindowAppearing",
"IsWindowCollapsed",
"IsWindowFocused",
"IsWindowHovered",
"GetWindowPos",
"GetWindowSize",
"GetWindowWidth",
"GetWindowHeight",
"Text",
]
kEnumWhitelist = [
"ImGuiWindowFlags_",
]
kLuaTypeChecker = {
"int": "LUA_TNUMBER == lua_type(L, #)",
"const char *": "LUA_TSTRING == lua_type(L, #)",
"$": "lua_isnone(L, #)",
}
# ---- ENTRYPOINT
proc = subprocess.run(["clang++", "-x", "c++", "-std=c++2b", "-Xclang", "-ast-dump=json", "-fsyntax-only", sys.argv[1]], capture_output=True)
if 0 == proc.returncode:
walker = Walker()
walker.walk(json.loads(proc.stdout))
walker.emit()
else:
print(proc.stderr.decode("utf-8"))
exit(1)

@ -3,6 +3,7 @@ target_link_libraries(nf7_core
PUBLIC
git_hash
imgui
imgui4lua
luajit
nf7_config
nf7_iface

@ -12,7 +12,15 @@ namespace nf7::core::imgui {
std::shared_ptr<luajit::Value> LuaJITDriver::MakeExtensionObject(
luajit::TaskContext& lua) {
lua_pushnil(*lua);
auto L = *lua;
lua_newuserdata(L, 0);
if (luaL_newmetatable(L, "nf7::core::imgui::LuaJITDriver::Extension")) {
lua_createtable(L, 0, 0);
# include "generated/imgui4lua.inc"
lua_setfield(L, -2, "__index");
}
lua_setmetatable(L, -2);
return lua.Register();
}

@ -38,15 +38,17 @@ TEST_F(ImGuiLuaJITDriver, CompileAndInstall) {
auto fu = nf7::core::imgui::LuaJITDriver::CompileAndInstall(
*subenv,
toVector("local ctx = ...\nctx:trace(\"hello world\")"),
toVector(
"local ctx = ...\nctx:trace(\"hello world\")\n"
"local imgui = ctx:recv():lua()\n"
"imgui.Begin(\"helloworld\", true, imgui.WindowFlags_NoResize)\n"
"imgui.End()"),
"test chunk");
concurrency->Push(
nf7::SyncTask {
clock->now() + std::chrono::seconds {2},
clock->now() + std::chrono::seconds {10},
[&](auto&) { DropEnv(); },
});
ConsumeTasks();
// TODO FIXME fix leak
}

@ -1,6 +1,7 @@
// No copyright
#include "core/luajit/context.hh"
#include <atomic>
#include <mutex>
#include <vector>
@ -39,6 +40,17 @@ void TaskContext::Push(const nf7::Value& v) noexcept {
return 1;
});
lua_setfield(state_, -2, "type");
lua_pushcfunction(state_, [](auto L) {
const nf7::Value& v = CheckUserData<nf7::Value>(L, 1, "nf7::Value");
try {
v.data<luajit::Value>()->Push(L);
} catch (...) {
lua_pushnil(L);
}
return 1;
});
lua_setfield(state_, -2, "lua");
}
lua_setfield(state_, -2, "__index");
@ -84,13 +96,16 @@ class SyncContext final :
}
void Push(Task&& task) noexcept override {
++refcnt_;
auto self = std::dynamic_pointer_cast<SyncContext>(shared_from_this());
concurrency_->Push({
task.after(),
[self, task = std::move(task)](auto&) mutable {
[this, self, task = std::move(task)](auto&) mutable {
TaskContext ctx {self, self->state()};
lua_settop(*ctx, 0);
task(ctx);
if (0 == --refcnt_) { lua_gc(*ctx, LUA_GCCOLLECT, 0); }
},
task.location()
});
@ -100,7 +115,8 @@ class SyncContext final :
using Context::shared_from_this;
private:
std::shared_ptr<subsys::Concurrency> concurrency_;
const std::shared_ptr<subsys::Concurrency> concurrency_;
uint64_t refcnt_ {0};
};
class AsyncContext final :
@ -114,6 +130,8 @@ class AsyncContext final :
}
void Push(Task&& task) noexcept override {
++refcnt_;
std::unique_lock<std::mutex> k {mtx_};
const auto first = tasks_.empty();
tasks_.push_back(std::move(task));
@ -142,7 +160,9 @@ class AsyncContext final :
TaskContext ctx {self, state()};
for (auto& task : tasks) {
task(ctx);
--refcnt_;
}
if (0 == refcnt_) { lua_gc(*ctx, LUA_GCCOLLECT, 0); }
}
private:
@ -150,6 +170,8 @@ class AsyncContext final :
std::mutex mtx_;
std::vector<Task> tasks_;
std::atomic<uint64_t> refcnt_ {0};
};
} // namespace

@ -9,6 +9,8 @@
#include <string_view>
#include <vector>
#include <lua.hpp>
#include "iface/common/future.hh"
#include "iface/common/leak_detector.hh"
#include "iface/common/value.hh"
@ -45,13 +47,17 @@ class Value final : public nf7::Value::Data, public LeakDetector<Value> {
Value& operator=(const Value&) = delete;
Value& operator=(Value&&) = delete;
void Push(lua_State* L) const noexcept {
lua_rawgeti(L, LUA_REGISTRYINDEX, index_);
}
public:
const std::shared_ptr<Context>& context() const noexcept { return ctx_; }
int index() const noexcept { return index_; }
private:
std::shared_ptr<Context> ctx_;
int index_;
const std::shared_ptr<Context> ctx_;
const int index_;
};
} // namespace nf7::core::luajit

@ -19,6 +19,7 @@ FetchContent_Declare(
)
FetchContent_Populate(imgui)
include(imgui.cmake)
include(../cmake/imgui4lua.cmake)
# ---- luajit (MIT)
FetchContent_Declare(