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")
function func_to_executed(param)
local module = {}
function module.func_to_be_executed(param)
-- return tm.gd(param)
return param
end
return module

View File

@@ -76,7 +76,7 @@ class LuaFuncExecEngine:
lua_env = tm.gd("lua_env", {})
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.

View File

@@ -33,20 +33,34 @@ local function _get_func_by_path(file_path, func_name)
return target_func
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
-- 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")
pfile = utils.join_paths(td, file)
end
-- 2. retrieve the function "fname"
local func, err = _get_func_by_path(pfile, fname)
-- 3. Execute the function
local res = nil
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
-- 4. Returns result

View File

@@ -4,12 +4,14 @@ local utils = require("utils")
local JSONRPC = {}
JSONRPC.__index = JSONRPC
function JSONRPC.new(send_fn)
function JSONRPC.new(sock)
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.pending = {} -- Requests sent to client waiting for response
self.next_id = 1
self.sock:settimeout(0.2)
return self
end
@@ -25,8 +27,9 @@ function JSONRPC:handle_message(raw_data)
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')
if msg.result ~= nil or msg.error ~= nil then
return self:_handle_response(msg)
if (msg.id ~= nil) and (msg.result ~= nil or msg.error ~= nil) then
self.pending[msg.id] = msg
return
end
-- 2. Check if it's a Request
@@ -38,67 +41,72 @@ end
--- INTERNAL: Handle requests from the client
function JSONRPC:_handle_request(req)
local method = self.methods[req.method]
local ok, ret
local res, err
if not method then
if req.id then self:_send_error(req.id, -32601, "Method not found") end
return
end
local ok, result = pcall(method, req.params)
utils.log("calling '%s'", method)
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)
if req.id 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
self:_send_error(req.id, -32603, "Internal error: " .. tostring(result))
self:_send_error(req.id, -32603, "Internal error: " .. tostring(ret))
end
end
end
--- INTERNAL: Handle responses to requests WE sent
function JSONRPC:_handle_response(res)
local callback = self.pending[res.id]
if callback then
callback(res.error, res.result)
self.pending[res.id] = nil
end
end
-- function JSONRPC:_handle_response(res)
-- local callback = self.pending[res.id]
-- if callback then
-- callback(res.error, res.result)
-- self.pending[res.id] = nil
-- end
-- end
--- Call a method on the client
function JSONRPC:_call(method, params, callback)
function JSONRPC:call(method, params)
local id = self.next_id
self.next_id = self.next_id + 1
if callback then
self.pending[id] = callback
end
self:_send({
jsonrpc = "2.0",
method = method,
params = params,
id = id
})
end
function JSONRPC:call_sync(method, params)
local callco = coroutine.create(function(m, p)
local co = coroutine.running()
-- 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)
-- ---- Wait for response (re-entrant loop)
while true do
self:poll()
-- Pause execution here until 'resume' is called
return coroutine.yield()
end)
return coroutine.resume(callco, method, params)
if self.pending[id] then
local resp = self.pending[id]
self.pending[id] = nil
if resp.error then
error(resp.error.message)
end
return resp.result
end
end
end
function JSONRPC:_send(data)
local j = json.encode(data)
utils.log("sending: '%s'", j)
self.send_raw(j)
return self.sock:send(j .. "\n")
end
function JSONRPC:_send_error(id, code, message)
@@ -109,4 +117,24 @@ function JSONRPC:_send_error(id, code, message)
})
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

View File

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

View File

@@ -14,7 +14,7 @@ function tm._init_api(rpc)
local function _api_request(fname, ...)
local args = {...}
return tm._rpc:call_sync(fname, args)
return tm._rpc:call(fname, args)
end
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
-- 1. Check for POSIX absolute path (starts with /)
if path:sub(1, 1) == "/" then
if string.sub(path, 1, 1) == "/" then
return true
end
-- 2. Check for Windows drive letter (e.g., C:\ or D:/)
-- 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
end
-- 3. Check for Windows UNC/Network paths (starts with \\ or //)
if path:match("^[/\\][/\\]") then
if string.match(path, "^[/\\][/\\]") then
return true
end
@@ -61,7 +61,7 @@ function utils.setup_remote_print(rpc)
message = message .. "\n"
end
pcall(function()
rpc:call_sync("print", message )
rpc:call("print", message )
end)
-- Optional: Still print to the server's local console
-- utils.log("[Remote Log Sent]: " .. message)