GodotPythonJSONRPC/addons/kiripythonrpcwrapper/KiriPythonRPCWrapper/__init__.py

155 lines
4.2 KiB
Python
Raw Normal View History

#!/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)