close to the lua func goal.

This commit is contained in:
2025-12-30 19:30:19 +01:00
parent fb041f6570
commit f54e09098a
7 changed files with 95 additions and 65 deletions

View File

@@ -1,6 +1,10 @@
tm = require("tm") tm = require("tm")
function func_to_executed(param) local module = {}
function module.func_to_be_executed(param)
-- return tm.gd(param) -- return tm.gd(param)
return param return param
end end
return module

View File

@@ -76,7 +76,7 @@ class LuaFuncExecEngine:
lua_env = tm.gd("lua_env", {}) lua_env = tm.gd("lua_env", {})
self._process = subprocess.Popen( self._process = subprocess.Popen(
[self._lpath, "main.lua", "--host", "localhost", "--port", f"{self._port}"], env=lua_env, cwd=func_proc_path [self._lpath, "main.lua", "--host", "127.0.0.1", "--port", f"{self._port}"], env=lua_env, cwd=func_proc_path
) )
# Port was reserved until the sub-process is started. Now released. # Port was reserved until the sub-process is started. Now released.

View File

@@ -33,20 +33,34 @@ local function _get_func_by_path(file_path, func_name)
return target_func return target_func
end end
function handle.func_call(file, fname, params) function handle.func_call(params)
local file = params.file
local fname = params.fname
local prms = params.params
local res = nil
local succ, ret
local pfile = file local pfile = file
-- 1. modify the file path if it is relative -- 1. modify the file path if it is relative
if utils.is_relative_path(file) then if utils.is_relative_path(pfile) then
local td = tm.gd("test_directory") local td = tm.gd("test_directory")
pfile = utils.join_paths(td, file) pfile = utils.join_paths(td, file)
end end
-- 2. retrieve the function "fname" -- 2. retrieve the function "fname"
local func, err = _get_func_by_path(pfile, fname) local func, err = _get_func_by_path(pfile, fname)
-- 3. Execute the function -- 3. Execute the function
local res = nil
if err == nil then if err == nil then
succ, res = pcall(func, table.unpack(params)) utils.log("func_call function found '%s', '%s'", file, fname)
succ, ret = pcall(func, table.unpack(prms))
utils.log("func_call returned '%s'", tostring(ret))
if succ then
res = ret
else
err = ret
end
end end
-- 4. Returns result -- 4. Returns result

View File

@@ -4,12 +4,14 @@ local utils = require("utils")
local JSONRPC = {} local JSONRPC = {}
JSONRPC.__index = JSONRPC JSONRPC.__index = JSONRPC
function JSONRPC.new(send_fn) function JSONRPC.new(sock)
local self = setmetatable({}, JSONRPC) local self = setmetatable({}, JSONRPC)
self.send_raw = send_fn -- Function to transmit string data to transport (TCP/Websocket) self.sock = sock -- Function to transmit string data to transport (TCP/Websocket)
self.methods = {} -- Methods the server provides to the client self.methods = {} -- Methods the server provides to the client
self.pending = {} -- Requests sent to client waiting for response self.pending = {} -- Requests sent to client waiting for response
self.next_id = 1 self.next_id = 1
self.sock:settimeout(0.2)
return self return self
end end
@@ -25,8 +27,9 @@ function JSONRPC:handle_message(raw_data)
if not ok then return self:_send_error(nil, -32700, "Parse error") end if not ok then return self:_send_error(nil, -32700, "Parse error") end
-- 1. Check if it's a Response (has 'result' or 'error' and 'id') -- 1. Check if it's a Response (has 'result' or 'error' and 'id')
if msg.result ~= nil or msg.error ~= nil then if (msg.id ~= nil) and (msg.result ~= nil or msg.error ~= nil) then
return self:_handle_response(msg) self.pending[msg.id] = msg
return
end end
-- 2. Check if it's a Request -- 2. Check if it's a Request
@@ -38,67 +41,72 @@ end
--- INTERNAL: Handle requests from the client --- INTERNAL: Handle requests from the client
function JSONRPC:_handle_request(req) function JSONRPC:_handle_request(req)
local method = self.methods[req.method] local method = self.methods[req.method]
local ok, ret
local res, err
if not method then if not method then
if req.id then self:_send_error(req.id, -32601, "Method not found") end if req.id then self:_send_error(req.id, -32601, "Method not found") end
return return
end end
utils.log("calling '%s'", method)
local ok, result = pcall(method, req.params) ok, ret = pcall(method, req.params)
utils.log("returned '%s', '%s'", tostring(ok), tostring(res))
-- Only send response if it's not a Notification (notifications have no ID) -- Only send response if it's not a Notification (notifications have no ID)
if req.id then if req.id then
if ok then if ok then
self:_send({ jsonrpc = "2.0", result = result, id = req.id }) res, err = ret
if res == nil then
self:_send_error(req.id, -32603, "Internal error: " .. tostring(err))
else
self:_send({ jsonrpc = "2.0", result = {returned = res}, id = req.id })
end
else else
self:_send_error(req.id, -32603, "Internal error: " .. tostring(result)) self:_send_error(req.id, -32603, "Internal error: " .. tostring(ret))
end end
end end
end end
--- INTERNAL: Handle responses to requests WE sent --- INTERNAL: Handle responses to requests WE sent
function JSONRPC:_handle_response(res) -- function JSONRPC:_handle_response(res)
local callback = self.pending[res.id] -- local callback = self.pending[res.id]
if callback then -- if callback then
callback(res.error, res.result) -- callback(res.error, res.result)
self.pending[res.id] = nil -- self.pending[res.id] = nil
end -- end
end -- end
--- Call a method on the client --- Call a method on the client
function JSONRPC:_call(method, params, callback) function JSONRPC:call(method, params)
local id = self.next_id local id = self.next_id
self.next_id = self.next_id + 1 self.next_id = self.next_id + 1
if callback then
self.pending[id] = callback
end
self:_send({ self:_send({
jsonrpc = "2.0", jsonrpc = "2.0",
method = method, method = method,
params = params, params = params,
id = id id = id
}) })
end
function JSONRPC:call_sync(method, params) -- ---- Wait for response (re-entrant loop)
local callco = coroutine.create(function(m, p) while true do
local co = coroutine.running() self:poll()
-- Call the async version, but use the callback to resume this coroutine
self:_call(m, p, function(err, res)
coroutine.resume(co, err, res)
end)
-- Pause execution here until 'resume' is called if self.pending[id] then
return coroutine.yield() local resp = self.pending[id]
end) self.pending[id] = nil
return coroutine.resume(callco, method, params)
if resp.error then
error(resp.error.message)
end
return resp.result
end
end
end end
function JSONRPC:_send(data) function JSONRPC:_send(data)
local j = json.encode(data) local j = json.encode(data)
utils.log("sending: '%s'", j) utils.log("sending: '%s'", j)
self.send_raw(j) return self.sock:send(j .. "\n")
end end
function JSONRPC:_send_error(id, code, message) function JSONRPC:_send_error(id, code, message)
@@ -109,4 +117,24 @@ function JSONRPC:_send_error(id, code, message)
}) })
end end
function JSONRPC:poll()
local line, err = self.sock:receive("*l")
if line then
self:handle_message(line)
elseif err ~= "timeout" and err ~= nil then
utils.log("Connection ended: %s", err)
return false
end
return true
end
function JSONRPC:loop()
while true do
if not self:poll() then
break
end
end
end
return JSONRPC return JSONRPC

View File

@@ -59,6 +59,7 @@ local socket = require("socket")
local JSONRPC = require("json-rpc") -- The module from the previous response local JSONRPC = require("json-rpc") -- The module from the previous response
local utils = require("utils") local utils = require("utils")
local tm = require("tm") local tm = require("tm")
local handle = require("handle")
utils.verbose = config.verbose utils.verbose = config.verbose
@@ -75,35 +76,18 @@ if err then
os.exit(0) os.exit(0)
end end
client_sock:settimeout(10) -- Prevents hanging on dead connections
utils.log("Client connected") utils.log("Client connected")
-- Initialize the RPC instance for this specific connection -- Initialize the RPC instance for this specific connection
local rpc = JSONRPC.new(function(data) local rpc = JSONRPC.new(client_sock)
client_sock:send(data .. "\n") -- Standard JSON-RPC uses newline delimiters over TCP
end)
tm._init_api(rpc) tm._init_api(rpc)
utils.setup_remote_print(rpc) utils.setup_remote_print(rpc)
-- Example: Send a request TO the client immediately upon connection rpc:register("func_call", handle.func_call)
print("Welcome to the lua server")
local tdir = tm.gd("test_directory")
-- Communication Loop for this client -- Communication Loop for this client
while true do rpc:loop()
local line, err = client_sock:receive() -- Read until newline
if err == "closed" then
utils.log("Connection ended: %s", err)
break
elseif err then
socket.sleep(0.01)
else
rpc:handle_message(line)
end
end
client_sock:close() client_sock:close()
utils.log("Server stopped") utils.log("Server stopped")

View File

@@ -14,7 +14,7 @@ function tm._init_api(rpc)
local function _api_request(fname, ...) local function _api_request(fname, ...)
local args = {...} local args = {...}
return tm._rpc:call_sync(fname, args) return tm._rpc:call(fname, args)
end end
for _, fname in ipairs(SUPPORTED_API) do for _, fname in ipairs(SUPPORTED_API) do

View File

@@ -19,18 +19,18 @@ function utils.is_absolute_path(path)
if not path or path == "" then return false end if not path or path == "" then return false end
-- 1. Check for POSIX absolute path (starts with /) -- 1. Check for POSIX absolute path (starts with /)
if path:sub(1, 1) == "/" then if string.sub(path, 1, 1) == "/" then
return true return true
end end
-- 2. Check for Windows drive letter (e.g., C:\ or D:/) -- 2. Check for Windows drive letter (e.g., C:\ or D:/)
-- Pattern: %a (letter) followed by : (colon) -- Pattern: %a (letter) followed by : (colon)
if path:match("^%a:[/\\]") or path:match("^%a:$") then if string.match(path, "^%a:[/\\]") or string.match(path, "^%a:$") then
return true return true
end end
-- 3. Check for Windows UNC/Network paths (starts with \\ or //) -- 3. Check for Windows UNC/Network paths (starts with \\ or //)
if path:match("^[/\\][/\\]") then if string.match(path, "^[/\\][/\\]") then
return true return true
end end
@@ -61,7 +61,7 @@ function utils.setup_remote_print(rpc)
message = message .. "\n" message = message .. "\n"
end end
pcall(function() pcall(function()
rpc:call_sync("print", message ) rpc:call("print", message )
end) end)
-- Optional: Still print to the server's local console -- Optional: Still print to the server's local console
-- utils.log("[Remote Log Sent]: " .. message) -- utils.log("[Remote Log Sent]: " .. message)