155 lines
4.2 KiB
Python
155 lines
4.2 KiB
Python
|
#!/usr/bin/python3
|
||
|
|
||
|
import importlib.util
|
||
|
import sys
|
||
|
import argparse
|
||
|
import time
|
||
|
import psutil
|
||
|
import json
|
||
|
|
||
|
import KiriPacketSocket
|
||
|
|
||
|
# Parse arguments
|
||
|
arg_parser = argparse.ArgumentParser(
|
||
|
prog="KiriPythonRPCWrapper",
|
||
|
description="Wrapper for Python modules to RPCs from Godot.",
|
||
|
epilog="")
|
||
|
|
||
|
arg_parser.add_argument("--script", type=str, required=True)
|
||
|
arg_parser.add_argument("--port", type=int, required=True)
|
||
|
arg_parser.add_argument("--parent_pid", type=int, required=True)
|
||
|
|
||
|
args = arg_parser.parse_args()
|
||
|
|
||
|
|
||
|
# module_path = "../KiriPacketSocket/__init__.py"
|
||
|
# module_name = "KiriPacketSocket"
|
||
|
|
||
|
module_path = args.script
|
||
|
module_name = ""
|
||
|
|
||
|
# Attempt to load the module.
|
||
|
module_spec = importlib.util.spec_from_file_location(
|
||
|
module_name, module_path)
|
||
|
module = importlib.util.module_from_spec(module_spec)
|
||
|
module_spec.loader.exec_module(module)
|
||
|
|
||
|
# This will be all the functions we find in the module that don't
|
||
|
# start with "_".
|
||
|
known_entrypoints = {}
|
||
|
|
||
|
# Scan the module for "public" functions.
|
||
|
for entrypoint in dir(module):
|
||
|
|
||
|
# Skip anything starting with "_". Probably not meant to be
|
||
|
# exposed.
|
||
|
if entrypoint.startswith("_"):
|
||
|
continue
|
||
|
|
||
|
attr = getattr(module, entrypoint)
|
||
|
|
||
|
# if hasattr(attr, "__call__"):
|
||
|
if callable(attr):
|
||
|
known_entrypoints[entrypoint] = attr
|
||
|
|
||
|
# Connect to server.
|
||
|
packet_socket = KiriPacketSocket.PacketSocket()
|
||
|
packet_socket.start_client(("127.0.0.1", args.port))
|
||
|
while packet_socket.get_state() == packet_socket.SocketState.CONNECTING:
|
||
|
time.sleep(0.001)
|
||
|
|
||
|
if packet_socket.get_state() != packet_socket.SocketState.CONNECTED:
|
||
|
packet_socket.stop()
|
||
|
raise Exception("Failed to connect to RPC host.")
|
||
|
|
||
|
print("Starting packet processing.")
|
||
|
|
||
|
|
||
|
|
||
|
def send_error_response(code, message, request_id):
|
||
|
ret_dict = {
|
||
|
"jsonrpc" : "2.0",
|
||
|
"error" : {
|
||
|
"code" : code,
|
||
|
"message" : message
|
||
|
},
|
||
|
"id" : request_id
|
||
|
}
|
||
|
ret_dict_json = json.dumps(ret_dict)
|
||
|
packet_socket.send_packet(ret_dict_json.encode("utf-8"))
|
||
|
|
||
|
def send_response(result, request_id):
|
||
|
try:
|
||
|
ret_dict = {
|
||
|
"jsonrpc" : "2.0",
|
||
|
"result" : ret,
|
||
|
"id" : request_id
|
||
|
}
|
||
|
ret_dict_json = json.dumps(ret_dict)
|
||
|
packet_socket.send_packet(ret_dict_json.encode("utf-8"))
|
||
|
except Exception as e:
|
||
|
send_error_response(-32603, "Error sending result: " + str(e), request_id)
|
||
|
|
||
|
# Start processing packets.
|
||
|
while True:
|
||
|
|
||
|
# Shutdown when we lose connection to host.
|
||
|
if packet_socket.get_state() != packet_socket.SocketState.CONNECTED:
|
||
|
packet_socket.stop()
|
||
|
raise Exception("Disconnected from RPC host.")
|
||
|
|
||
|
# Watch parent PID so we can clean up when needed.
|
||
|
if not psutil.pid_exists(args.parent_pid):
|
||
|
packet_socket.stop()
|
||
|
raise Exception("RPC host process died")
|
||
|
|
||
|
next_packet = packet_socket.get_next_packet()
|
||
|
while next_packet:
|
||
|
this_packet = next_packet
|
||
|
next_packet = packet_socket.get_next_packet()
|
||
|
|
||
|
print("GOT PACKET: ", this_packet)
|
||
|
|
||
|
# FIXME: Handle batches.
|
||
|
|
||
|
# Parse the incoming dict.
|
||
|
try:
|
||
|
request_dict_json = this_packet.decode("utf-8")
|
||
|
request_dict = json.loads(request_dict_json)
|
||
|
except Exception as e:
|
||
|
send_error_response(-32700, "Error parsing packet: " + str(e), request_id)
|
||
|
continue
|
||
|
|
||
|
# Make sure all the fields are there.
|
||
|
try:
|
||
|
method = request_dict["method"]
|
||
|
func_args = request_dict["params"]
|
||
|
request_id = request_dict["id"]
|
||
|
except Exception as e:
|
||
|
send_error_response(-32602, "Missing field: " + str(e), request_id)
|
||
|
continue
|
||
|
|
||
|
# Make sure the method is something we scanned earlier.
|
||
|
try:
|
||
|
func = known_entrypoints[method]
|
||
|
except Exception as e:
|
||
|
send_error_response(-32601, "Method not found: " + str(e), request_id)
|
||
|
continue
|
||
|
|
||
|
# Call the dang function.
|
||
|
try:
|
||
|
ret = func(*func_args)
|
||
|
except Exception as e:
|
||
|
send_error_response(-32603, "Call failed: " + str(e), request_id)
|
||
|
continue
|
||
|
|
||
|
send_response(ret, request_id)
|
||
|
|
||
|
time.sleep(0.0001)
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|