220 lines
6.1 KiB
GDScript3
220 lines
6.1 KiB
GDScript3
|
extends RefCounted
|
||
|
class_name KiriPythonWrapperInstance
|
||
|
|
||
|
var external_process_pid = -1
|
||
|
|
||
|
var server_packet_socket : KiriPacketSocket = null
|
||
|
var communication_packet_socket : KiriPacketSocket = null
|
||
|
|
||
|
var python_script_path : String = ""
|
||
|
|
||
|
enum KiriPythonWrapperStatus {
|
||
|
STATUS_RUNNING,
|
||
|
STATUS_STOPPED
|
||
|
}
|
||
|
|
||
|
func _init(python_file_path : String):
|
||
|
python_script_path = python_file_path
|
||
|
|
||
|
func _get_python_executable():
|
||
|
# FIXME: Adjust per-OS. Maybe test a few locations.
|
||
|
return "/usr/bin/python3"
|
||
|
|
||
|
func _get_wrapper_script():
|
||
|
# FIXME: Paths will be different for builds.
|
||
|
var script_path = self.get_script().get_path()
|
||
|
var script_dirname = script_path.get_base_dir()
|
||
|
return ProjectSettings.globalize_path( \
|
||
|
script_dirname + "/KiriPythonRPCWrapper_start.py")
|
||
|
|
||
|
func get_status():
|
||
|
|
||
|
if external_process_pid == -1:
|
||
|
return KiriPythonWrapperStatus.STATUS_STOPPED
|
||
|
|
||
|
if not OS.is_process_running(external_process_pid):
|
||
|
return KiriPythonWrapperStatus.STATUS_STOPPED
|
||
|
|
||
|
return KiriPythonWrapperStatus.STATUS_RUNNING
|
||
|
|
||
|
func start_process():
|
||
|
|
||
|
# FIXME: Make sure we don't have one running.
|
||
|
|
||
|
var open_port = 9500
|
||
|
|
||
|
assert(not server_packet_socket)
|
||
|
server_packet_socket = KiriPacketSocket.new()
|
||
|
while true:
|
||
|
server_packet_socket.start_server(["127.0.0.1", open_port])
|
||
|
|
||
|
# Wait for the server to start.
|
||
|
while server_packet_socket.get_state() == KiriPacketSocket.KiriSocketState.SERVER_STARTING:
|
||
|
OS.delay_usec(1)
|
||
|
|
||
|
# If we're successfully listening, then we found a port to use and we
|
||
|
# don't need to loop anymore.
|
||
|
if server_packet_socket.get_state() == KiriPacketSocket.KiriSocketState.SERVER_LISTENING:
|
||
|
break
|
||
|
|
||
|
# This port is busy. Try the next one.
|
||
|
server_packet_socket.stop()
|
||
|
open_port += 1
|
||
|
|
||
|
print("Port: ", open_port)
|
||
|
|
||
|
var python_exe_path : String = _get_python_executable()
|
||
|
var wrapper_script_path : String = _get_wrapper_script()
|
||
|
|
||
|
var startup_command : Array = [
|
||
|
"xterm", "-e",
|
||
|
python_exe_path,
|
||
|
wrapper_script_path,
|
||
|
"--script", python_script_path,
|
||
|
"--port", open_port,
|
||
|
"--parent_pid", OS.get_process_id()]
|
||
|
|
||
|
print("startup command: ", startup_command)
|
||
|
|
||
|
external_process_pid = OS.create_process(
|
||
|
startup_command[0], startup_command.slice(1), true)
|
||
|
|
||
|
print("external process: ", external_process_pid)
|
||
|
|
||
|
func stop_process():
|
||
|
|
||
|
if external_process_pid != -1:
|
||
|
OS.kill(external_process_pid)
|
||
|
external_process_pid = -1
|
||
|
|
||
|
|
||
|
# Clean up server and communication sockets.
|
||
|
if server_packet_socket:
|
||
|
server_packet_socket.stop()
|
||
|
server_packet_socket = null
|
||
|
|
||
|
if communication_packet_socket:
|
||
|
communication_packet_socket.stop()
|
||
|
communication_packet_socket = null
|
||
|
|
||
|
|
||
|
class KiriPythonWrapperActiveRequest:
|
||
|
|
||
|
enum KiriPythonWrapperActiveRequestState {
|
||
|
STATE_WAITING_TO_SEND,
|
||
|
STATE_SENT,
|
||
|
STATE_RESPONSE_RECEIVED
|
||
|
}
|
||
|
|
||
|
var id : int
|
||
|
var method_name : String
|
||
|
var arguments : Variant # Dictionary or Array
|
||
|
var callback # Callable or null
|
||
|
var state : KiriPythonWrapperActiveRequestState
|
||
|
var response # Return value from the call
|
||
|
var error_response = ""
|
||
|
|
||
|
var _active_request_queue = {}
|
||
|
var _request_counter = 0
|
||
|
|
||
|
func call_rpc_async(method : String, args : Variant, callback = null) -> int:
|
||
|
|
||
|
assert((args is Dictionary) or (args is Array))
|
||
|
assert((callback == null) or (callback is Callable))
|
||
|
|
||
|
var new_request = KiriPythonWrapperActiveRequest.new()
|
||
|
new_request.id = _request_counter
|
||
|
_request_counter += 1
|
||
|
new_request.method_name = method
|
||
|
new_request.arguments = args
|
||
|
new_request.callback = callback
|
||
|
|
||
|
_active_request_queue[new_request.id] = new_request
|
||
|
|
||
|
return new_request.id
|
||
|
|
||
|
func call_rpc_sync(method : String, args : Variant):
|
||
|
|
||
|
# Kinda hacky. We're using arrays because we can change the contents.
|
||
|
# Without the array or something else mutable we'd just end up with the
|
||
|
# internal pointer pointing to different values without affecting these
|
||
|
# ones.
|
||
|
var done_array = [false]
|
||
|
var response_list = []
|
||
|
var request_id = call_rpc_async(method, args, func(request_ob):
|
||
|
done_array[0] = true
|
||
|
response_list.append(request_ob.response)
|
||
|
)
|
||
|
|
||
|
# Wait (block) until we get a response.
|
||
|
while not done_array[0]:
|
||
|
|
||
|
# Bail out if something happened to our instance or connection to it.
|
||
|
if communication_packet_socket:
|
||
|
if communication_packet_socket.is_disconnected_or_error():
|
||
|
break
|
||
|
|
||
|
poll()
|
||
|
OS.delay_usec(1)
|
||
|
|
||
|
if len(response_list):
|
||
|
return response_list[0]
|
||
|
|
||
|
return null
|
||
|
|
||
|
func poll():
|
||
|
|
||
|
# Hand-off between listening socket and actual communications socket.
|
||
|
if server_packet_socket:
|
||
|
communication_packet_socket = server_packet_socket.get_next_server_connection()
|
||
|
if communication_packet_socket:
|
||
|
server_packet_socket.stop()
|
||
|
server_packet_socket = null
|
||
|
|
||
|
if communication_packet_socket:
|
||
|
|
||
|
# Send all waiting requests
|
||
|
for request_id in _active_request_queue:
|
||
|
var request : KiriPythonWrapperActiveRequest = _active_request_queue[request_id]
|
||
|
if request.state == request.KiriPythonWrapperActiveRequestState.STATE_WAITING_TO_SEND:
|
||
|
var request_dict = {
|
||
|
"jsonrpc": "2.0",
|
||
|
"method": request.method_name,
|
||
|
"params": request.arguments,
|
||
|
"id": request.id
|
||
|
}
|
||
|
var request_dict_json = JSON.stringify(request_dict)
|
||
|
communication_packet_socket.send_packet(request_dict_json.to_utf8_buffer())
|
||
|
request.state = request.KiriPythonWrapperActiveRequestState.STATE_SENT
|
||
|
|
||
|
# Check for responses.
|
||
|
var packet = communication_packet_socket.get_next_packet()
|
||
|
while packet != null:
|
||
|
var packet_dict = JSON.parse_string(packet.get_string_from_utf8())
|
||
|
if packet_dict:
|
||
|
if packet_dict.has("id"):
|
||
|
var request_id = packet_dict["id"]
|
||
|
|
||
|
# floats aren't even allowed in JSON RPC as an id. Probably
|
||
|
# meant it to be an int.
|
||
|
if request_id is float:
|
||
|
request_id = int(request_id)
|
||
|
|
||
|
if _active_request_queue.has(request_id):
|
||
|
var request : KiriPythonWrapperActiveRequest = \
|
||
|
_active_request_queue[request_id]
|
||
|
if "result" in packet_dict:
|
||
|
request.response = packet_dict["result"]
|
||
|
else:
|
||
|
request.error_response = "Couldn't find result on packet."
|
||
|
if request.callback:
|
||
|
request.callback.call(request)
|
||
|
|
||
|
# Clean up request.
|
||
|
_active_request_queue.erase(request_id)
|
||
|
|
||
|
packet = communication_packet_socket.get_next_packet()
|
||
|
|
||
|
|
||
|
|