lliquidlink 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lliquidlink/__init__.py +9 -0
- lliquidlink/client/__init__.py +51 -0
- lliquidlink/client/_client.py +138 -0
- lliquidlink/client/_event.py +26 -0
- lliquidlink/client/_proxy.py +92 -0
- lliquidlink/client/_schema.py +40 -0
- lliquidlink/client/_serialization.py +26 -0
- lliquidlink/client/_transports.py +97 -0
- lliquidlink/client/models.py +17 -0
- lliquidlink/core/__init__.py +20 -0
- lliquidlink/core/_rpc.py +178 -0
- lliquidlink/server/__init__.py +29 -0
- lliquidlink/server/__main__.py +2 -0
- lliquidlink/server/_transport.py +95 -0
- lliquidlink/server/ipc_bridge.py +36 -0
- lliquidlink/server/resolver.py +204 -0
- lliquidlink/server/server.py +135 -0
- lliquidlink-1.0.0.dist-info/METADATA +176 -0
- lliquidlink-1.0.0.dist-info/RECORD +21 -0
- lliquidlink-1.0.0.dist-info/WHEEL +4 -0
- lliquidlink-1.0.0.dist-info/licenses/LICENSE +21 -0
lliquidlink/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""lliquidlink: Python side of the Unity JSON-RPC bridge.
|
|
2
|
+
|
|
3
|
+
Subpackages:
|
|
4
|
+
lliquidlink.core - transport-agnostic JSON-RPC core shared by client and server.
|
|
5
|
+
lliquidlink.client - Client that connects to a Unity Editor server.
|
|
6
|
+
lliquidlink.server - Server middleware bridging external clients to Unity over stdio.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""lliquidlink.client - Python client to control the Unity Editor over JSON-RPC.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
def on_execute(client):
|
|
6
|
+
go = client.Find("Player")
|
|
7
|
+
go.transform.Rotate(0, 90, 0)
|
|
8
|
+
|
|
9
|
+
client = Client(TcpJsonRpcTransport("localhost", 8700))
|
|
10
|
+
client.on_execute += on_execute
|
|
11
|
+
client.mainloop()
|
|
12
|
+
"""
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
from ._client import Client, gc_flush
|
|
16
|
+
from ._event import Event
|
|
17
|
+
from ._proxy import ObjectProxy, PropertyProxy
|
|
18
|
+
from ._transports import StdioJsonRpcTransport, TcpJsonRpcTransport
|
|
19
|
+
from ..core import ConnectionClosedError, RpcError
|
|
20
|
+
from . import models
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("lliquidlink.client")
|
|
23
|
+
|
|
24
|
+
def setup_logger():
|
|
25
|
+
level = logging.DEBUG
|
|
26
|
+
h = logging.StreamHandler()
|
|
27
|
+
h.setFormatter(logging.Formatter("[Client] %(message)s"))
|
|
28
|
+
h.setLevel(level)
|
|
29
|
+
logger.addHandler(h)
|
|
30
|
+
logger.setLevel(level)
|
|
31
|
+
for name in [
|
|
32
|
+
# "anyio", "websockets"
|
|
33
|
+
]:
|
|
34
|
+
_logger = logging.getLogger(name)
|
|
35
|
+
for h in logger.handlers:
|
|
36
|
+
_logger.addHandler(h)
|
|
37
|
+
_logger.setLevel(logger.level)
|
|
38
|
+
setup_logger()
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"Client",
|
|
42
|
+
"gc_flush",
|
|
43
|
+
"Event",
|
|
44
|
+
"ObjectProxy",
|
|
45
|
+
"PropertyProxy",
|
|
46
|
+
"StdioJsonRpcTransport",
|
|
47
|
+
"TcpJsonRpcTransport",
|
|
48
|
+
"ConnectionClosedError",
|
|
49
|
+
"RpcError",
|
|
50
|
+
"models",
|
|
51
|
+
]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Client: anyio runtime, RPC dispatch, object-release batching."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import functools
|
|
4
|
+
import json
|
|
5
|
+
import threading
|
|
6
|
+
import weakref
|
|
7
|
+
from typing import Any, List, TYPE_CHECKING
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ._transports import StreamTransport
|
|
10
|
+
|
|
11
|
+
import anyio
|
|
12
|
+
|
|
13
|
+
from ._event import Event
|
|
14
|
+
from ._proxy import ObjectProxy, PropertyProxy
|
|
15
|
+
from ._serialization import Serialization
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
async def _await(coro):
|
|
21
|
+
"""Await a coroutine; used to run it on the event loop from a worker thread."""
|
|
22
|
+
return await coro
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def gc_flush(func):
|
|
26
|
+
"""Decorator: flush pending Unity object releases after the method returns."""
|
|
27
|
+
@functools.wraps(func)
|
|
28
|
+
def wrapper(self, *args, **kwargs):
|
|
29
|
+
try:
|
|
30
|
+
return func(self, *args, **kwargs)
|
|
31
|
+
finally:
|
|
32
|
+
self.flush_releases()
|
|
33
|
+
return wrapper
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Client:
|
|
37
|
+
"""Client that connects to a Unity Editor server and sends RPC commands.
|
|
38
|
+
|
|
39
|
+
Register a handler on the :attr:`on_execute` event with ``+=``, then call
|
|
40
|
+
:meth:`mainloop`.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, transport : StreamTransport):
|
|
44
|
+
self._transport = transport
|
|
45
|
+
self._serialization = Serialization(lambda data: ObjectProxy(self, data))
|
|
46
|
+
transport.bind_codec(self._serialization)
|
|
47
|
+
self._pending_releases = set()
|
|
48
|
+
self._release_lock = threading.Lock()
|
|
49
|
+
self.on_execute = Event()
|
|
50
|
+
|
|
51
|
+
# ── RPC dispatch ─────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
def __getattr__(self, name: str) -> PropertyProxy:
|
|
54
|
+
"""Treat any undefined non-underscore attribute as a Unity RPC method."""
|
|
55
|
+
if name.startswith("_"):
|
|
56
|
+
raise AttributeError(name)
|
|
57
|
+
return PropertyProxy(self, None, [name])
|
|
58
|
+
|
|
59
|
+
async def _call_async(self, method: str, params: list) -> Any:
|
|
60
|
+
return await self._transport.rpc_call(method, params)
|
|
61
|
+
|
|
62
|
+
def _call_sync(self, method: str, params: list) -> Any:
|
|
63
|
+
return self._run(self._call_async(method, params))
|
|
64
|
+
|
|
65
|
+
def _run(self, coro):
|
|
66
|
+
"""Run a coroutine synchronously from a worker thread, else return it."""
|
|
67
|
+
try:
|
|
68
|
+
return anyio.from_thread.run(_await, coro)
|
|
69
|
+
except RuntimeError:
|
|
70
|
+
return coro
|
|
71
|
+
|
|
72
|
+
# ── Object release batching ──────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
def _track_release(self, proxy, data: dict) -> None:
|
|
75
|
+
instance_id = data.get("instanceId")
|
|
76
|
+
if instance_id is None:
|
|
77
|
+
return
|
|
78
|
+
weakref.finalize(proxy, self._schedule_release, instance_id)
|
|
79
|
+
|
|
80
|
+
def _schedule_release(self, instance_id: int) -> None:
|
|
81
|
+
with self._release_lock:
|
|
82
|
+
self._pending_releases.add(instance_id)
|
|
83
|
+
|
|
84
|
+
async def _flush_releases(self) -> None:
|
|
85
|
+
with self._release_lock:
|
|
86
|
+
if not self._pending_releases:
|
|
87
|
+
return
|
|
88
|
+
ids = sorted(self._pending_releases)
|
|
89
|
+
self._pending_releases.clear()
|
|
90
|
+
if self._transport.closed:
|
|
91
|
+
return
|
|
92
|
+
# await self._transport.rpc_notify("release_objects", [json.dumps(ids)])
|
|
93
|
+
|
|
94
|
+
def flush_releases(self) -> None:
|
|
95
|
+
"""Send pending object releases now (callable from a worker thread)."""
|
|
96
|
+
self._run(self._flush_releases())
|
|
97
|
+
|
|
98
|
+
# ── Lifecycle ────────────────────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def is_running(self) -> bool:
|
|
102
|
+
return not self._transport.closed
|
|
103
|
+
|
|
104
|
+
def mainloop(self) -> None:
|
|
105
|
+
"""Connect, run on_execute in a worker thread, flush, then disconnect."""
|
|
106
|
+
anyio.run(self._amain)
|
|
107
|
+
|
|
108
|
+
async def _amain(self) -> None:
|
|
109
|
+
await self._transport.open()
|
|
110
|
+
try:
|
|
111
|
+
await self.execute(self.on_execute)
|
|
112
|
+
await self._flush_releases()
|
|
113
|
+
finally:
|
|
114
|
+
await self._transport.aclose()
|
|
115
|
+
|
|
116
|
+
async def connect(self) -> None:
|
|
117
|
+
"""Open the connection to the Unity server."""
|
|
118
|
+
await self._transport.open()
|
|
119
|
+
|
|
120
|
+
async def disconnect(self) -> None:
|
|
121
|
+
"""Close the connection to the Unity server."""
|
|
122
|
+
await self._transport.aclose()
|
|
123
|
+
|
|
124
|
+
async def execute(self, on_execute) -> None:
|
|
125
|
+
"""Run a callback in a worker thread with this client as its argument."""
|
|
126
|
+
await anyio.to_thread.run_sync(on_execute, self)
|
|
127
|
+
|
|
128
|
+
def add_abbreviated_classes(self, class_names: List[str]) -> None:
|
|
129
|
+
"""Register a class name whose methods can be called without namespace prefix."""
|
|
130
|
+
if isinstance(class_names, str):
|
|
131
|
+
class_names = [class_names]
|
|
132
|
+
self._call_sync("add_abbreviated_classes", [class_names])
|
|
133
|
+
|
|
134
|
+
def add_abbreviated_namespaces(self, namespaces: List[str]) -> None:
|
|
135
|
+
"""Register namespaces whose types can be referred to by simple name."""
|
|
136
|
+
if isinstance(namespaces, str):
|
|
137
|
+
namespaces = [namespaces]
|
|
138
|
+
self._call_sync("add_abbreviated_namespaces", [namespaces])
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Minimal C#-style multicast event supporting += / -= handler registration."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import Callable, List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Event:
|
|
7
|
+
"""A list of callbacks invoked in registration order when called."""
|
|
8
|
+
|
|
9
|
+
def __init__(self) -> None:
|
|
10
|
+
self._handlers: List[Callable] = []
|
|
11
|
+
|
|
12
|
+
def __iadd__(self, handler: Callable) -> "Event":
|
|
13
|
+
self._handlers.append(handler)
|
|
14
|
+
return self
|
|
15
|
+
|
|
16
|
+
def __isub__(self, handler: Callable) -> "Event":
|
|
17
|
+
try:
|
|
18
|
+
self._handlers.remove(handler)
|
|
19
|
+
except ValueError:
|
|
20
|
+
pass
|
|
21
|
+
return self
|
|
22
|
+
|
|
23
|
+
def __call__(self, *args, **kwargs) -> None:
|
|
24
|
+
# Snapshot so a handler that mutates registration mid-dispatch is safe.
|
|
25
|
+
for handler in list(self._handlers):
|
|
26
|
+
handler(*args, **kwargs)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Lazy proxies for Unity object property/method access over RPC."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import Any, List
|
|
4
|
+
|
|
5
|
+
from .models import RpcChainStep
|
|
6
|
+
|
|
7
|
+
def _resolve_arg(arg):
|
|
8
|
+
if isinstance(arg, PropertyProxy):
|
|
9
|
+
return arg()
|
|
10
|
+
else:
|
|
11
|
+
return arg
|
|
12
|
+
|
|
13
|
+
def _resolve_args(args: List):
|
|
14
|
+
ret = []
|
|
15
|
+
for arg in args:
|
|
16
|
+
ret.append(_resolve_arg(arg))
|
|
17
|
+
return ret
|
|
18
|
+
|
|
19
|
+
class ObjectProxy:
|
|
20
|
+
"""Proxy for a live Unity object; attribute access builds RPC chains.
|
|
21
|
+
|
|
22
|
+
The raw server descriptor is kept on ``data``; garbage collection of this
|
|
23
|
+
proxy schedules a batched release of the underlying Unity object.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, client, data: dict):
|
|
27
|
+
object.__setattr__(self, "_client", client)
|
|
28
|
+
object.__setattr__(self, "data", data)
|
|
29
|
+
client._track_release(self, data)
|
|
30
|
+
|
|
31
|
+
def __getattr__(self, name: str) -> Any:
|
|
32
|
+
if name.startswith("_"):
|
|
33
|
+
raise AttributeError(name)
|
|
34
|
+
return PropertyProxy(self._client, self.data, [name])
|
|
35
|
+
|
|
36
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
37
|
+
if name.startswith("_") or name == "data":
|
|
38
|
+
object.__setattr__(self, name, value)
|
|
39
|
+
return
|
|
40
|
+
value = _resolve_arg(value)
|
|
41
|
+
self._client._call_sync("JsonRpc_ResolveChainSet", [self.data, [], name, value])
|
|
42
|
+
|
|
43
|
+
def __repr__(self) -> str:
|
|
44
|
+
return "ObjectProxy(%r)" % (self.data,)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PropertyProxy:
|
|
48
|
+
"""Accumulates a property/method chain, resolved server-side when called.
|
|
49
|
+
|
|
50
|
+
Examples::
|
|
51
|
+
|
|
52
|
+
obj.prop # proxy
|
|
53
|
+
obj.prop() # resolved value
|
|
54
|
+
obj.parent.child # chained getter
|
|
55
|
+
obj.GetComponent('Foo') # chained method call
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def __init__(self, client, obj, chain: List[str]):
|
|
59
|
+
object.__setattr__(self, "_client", client)
|
|
60
|
+
object.__setattr__(self, "_obj", obj)
|
|
61
|
+
object.__setattr__(self, "_chain", chain)
|
|
62
|
+
|
|
63
|
+
def __getattr__(self, name: str) -> "PropertyProxy":
|
|
64
|
+
if name.startswith("_"):
|
|
65
|
+
raise AttributeError(name)
|
|
66
|
+
return PropertyProxy(self._client, self._obj, self._chain + [name])
|
|
67
|
+
|
|
68
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
69
|
+
if name.startswith("_"):
|
|
70
|
+
object.__setattr__(self, name, value)
|
|
71
|
+
return
|
|
72
|
+
value = _resolve_arg(value)
|
|
73
|
+
steps = [RpcChainStep(p) for p in self._chain]
|
|
74
|
+
self._client._call_sync("JsonRpc_ResolveChainSet", [self._obj, steps, name, value])
|
|
75
|
+
|
|
76
|
+
async def _resolve(self, *args) -> Any:
|
|
77
|
+
"""Send the accumulated chain to Unity and return the resolved value."""
|
|
78
|
+
method = self._chain[-1]
|
|
79
|
+
params = list(args)
|
|
80
|
+
if (self._obj is None) and (len(self._chain) <= 1):
|
|
81
|
+
return await self._client._call_async(method, params)
|
|
82
|
+
steps = [RpcChainStep(p) for p in self._chain[:-1]]
|
|
83
|
+
return await self._client._call_async(
|
|
84
|
+
"JsonRpc_ResolveChain", [self._obj, steps, method, params])
|
|
85
|
+
|
|
86
|
+
def __call__(self, *args) -> Any:
|
|
87
|
+
"""Resolve synchronously (worker thread) or return a coroutine (async)."""
|
|
88
|
+
args = _resolve_args(args)
|
|
89
|
+
return self._client._run(self._resolve(*args))
|
|
90
|
+
|
|
91
|
+
def __await__(self):
|
|
92
|
+
return self._resolve().__await__()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""RPC wire-protocol type definitions.
|
|
2
|
+
|
|
3
|
+
Auto-generated by tools/generate_schema.py.
|
|
4
|
+
Do not edit manually. Edit tools/schema_def.py and re-run the generator.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any, Literal
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class RpcType:
|
|
14
|
+
"""Represents a .NET Type reference transmitted as a JSON-RPC parameter."""
|
|
15
|
+
|
|
16
|
+
value: str
|
|
17
|
+
rpcType: int = 1
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class RpcChainStep:
|
|
22
|
+
"""Single step in a property/method chain resolved server-side."""
|
|
23
|
+
|
|
24
|
+
name: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ReleaseRequest:
|
|
29
|
+
"""Batch request to release Unity object references held by the server."""
|
|
30
|
+
|
|
31
|
+
data_list: list[dict]
|
|
32
|
+
action: Literal['release_objects'] = 'release_objects'
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class RpcEnum:
|
|
37
|
+
"""Represents a .NET Type reference transmitted as a JSON-RPC parameter."""
|
|
38
|
+
|
|
39
|
+
value: str
|
|
40
|
+
rpcEnum: int = 1
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""JSON encode/decode hooks bridging Python values and the JSON-RPC wire format."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import dataclasses
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ._proxy import ObjectProxy
|
|
7
|
+
|
|
8
|
+
class Serialization:
|
|
9
|
+
"""Namespace for the json.dumps default and json.loads object_hook factories."""
|
|
10
|
+
def __init__(self, make_object_proxy):
|
|
11
|
+
self._make_object_proxy = make_object_proxy
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def encode_default(obj: Any) -> Any:
|
|
15
|
+
"""json.dumps default: ObjectProxy -> raw dict; dataclass -> dict."""
|
|
16
|
+
if isinstance(obj, ObjectProxy):
|
|
17
|
+
return obj.data
|
|
18
|
+
if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
|
|
19
|
+
return dataclasses.asdict(obj)
|
|
20
|
+
raise TypeError("Object of type %s is not JSON serializable" % type(obj).__name__)
|
|
21
|
+
|
|
22
|
+
def object_hook(self, d: dict):
|
|
23
|
+
"""Build a json.loads object_hook: InstanceObject dict -> ObjectProxy."""
|
|
24
|
+
if "instanceObjectAttr" in d.keys():
|
|
25
|
+
return self._make_object_proxy(d)
|
|
26
|
+
return d
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Transport implementations: byte-stream openers wrapping the JSON-RPC core.
|
|
2
|
+
|
|
3
|
+
Currently TCP and stdio. Unix-domain-socket can be added later by
|
|
4
|
+
subclassing ``StreamTransport`` and implementing ``_open()`` with
|
|
5
|
+
``anyio.connect_unix``.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
|
|
11
|
+
import anyio
|
|
12
|
+
|
|
13
|
+
from ..core import JsonRpcPeer, StdioByteStream
|
|
14
|
+
from typing import Any, TYPE_CHECKING
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from ._serialization import Serialization
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
class StreamTransport:
|
|
22
|
+
"""Base transport: opens a byte stream and runs a JsonRpcPeer over it.
|
|
23
|
+
|
|
24
|
+
The serve loop runs as a plain asyncio Task so open/aclose can be called
|
|
25
|
+
from different asyncio tasks without triggering anyio cancel-scope affinity
|
|
26
|
+
errors.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self._peer = None
|
|
31
|
+
self._serve_task = None
|
|
32
|
+
self._default = None
|
|
33
|
+
self._object_hook = None
|
|
34
|
+
|
|
35
|
+
def bind_codec(self, serialization : Serialization) -> None:
|
|
36
|
+
"""Set the json.dumps default / json.loads object_hook used by the peer."""
|
|
37
|
+
self._default = serialization.encode_default
|
|
38
|
+
self._object_hook = serialization.object_hook
|
|
39
|
+
|
|
40
|
+
async def _open(self):
|
|
41
|
+
"""Open and return the underlying byte stream. Override in subclasses."""
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
|
|
44
|
+
async def open(self) -> None:
|
|
45
|
+
stream = await self._open()
|
|
46
|
+
self._peer = JsonRpcPeer(stream, self._default, self._object_hook)
|
|
47
|
+
self._serve_task = asyncio.ensure_future(self._peer.serve())
|
|
48
|
+
|
|
49
|
+
async def aclose(self) -> None:
|
|
50
|
+
if self._peer is not None:
|
|
51
|
+
await self._peer.aclose()
|
|
52
|
+
self._peer = None
|
|
53
|
+
if self._serve_task is not None:
|
|
54
|
+
task = self._serve_task
|
|
55
|
+
self._serve_task = None
|
|
56
|
+
if not task.done():
|
|
57
|
+
task.cancel()
|
|
58
|
+
try:
|
|
59
|
+
await task
|
|
60
|
+
except (asyncio.CancelledError, Exception):
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def closed(self) -> bool:
|
|
65
|
+
return self._peer is None or self._peer.closed
|
|
66
|
+
|
|
67
|
+
async def rpc_call(self, method, params):
|
|
68
|
+
logger.debug("rpc_call %s(%s)", method, params)
|
|
69
|
+
ret = await self._peer.request(method, params)
|
|
70
|
+
logger.debug("rpc_call ret: %s", ret)
|
|
71
|
+
return ret
|
|
72
|
+
|
|
73
|
+
async def rpc_notify(self, method, params):
|
|
74
|
+
await self._peer.notify(method, params)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class StdioJsonRpcTransport(StreamTransport):
|
|
78
|
+
"""JSON-RPC transport over this process's stdin/stdout."""
|
|
79
|
+
|
|
80
|
+
async def _open(self):
|
|
81
|
+
return StdioByteStream()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TcpJsonRpcTransport(StreamTransport):
|
|
85
|
+
"""JSON-RPC transport over a raw TCP socket.
|
|
86
|
+
|
|
87
|
+
anyio's SocketStream already implements the send/receive/aclose
|
|
88
|
+
byte-stream interface JsonRpcPeer expects, so no adapter is needed.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(self, host: str, port: int):
|
|
92
|
+
super().__init__()
|
|
93
|
+
self._host = host
|
|
94
|
+
self._port = port
|
|
95
|
+
|
|
96
|
+
async def _open(self):
|
|
97
|
+
return await anyio.connect_tcp(self._host, self._port)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ._schema import *
|
|
4
|
+
|
|
5
|
+
def type_(value: str):
|
|
6
|
+
"""Create an RpcType parameter for passing a .NET Type to a Unity RPC method.
|
|
7
|
+
|
|
8
|
+
Args:
|
|
9
|
+
value: Fully qualified .NET type name, e.g. ``"UnityEngine.GameObject"``.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
An RpcType instance wrapping the given type name.
|
|
13
|
+
"""
|
|
14
|
+
return RpcType(value)
|
|
15
|
+
|
|
16
|
+
def enum(value: str):
|
|
17
|
+
return RpcEnum(value)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Role-agnostic JSON-RPC core shared by lliquidlink.client and lliquidlink.server."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from ._rpc import (
|
|
5
|
+
JsonRpcPeer,
|
|
6
|
+
StdioByteStream,
|
|
7
|
+
ConnectionClosedError,
|
|
8
|
+
RpcError,
|
|
9
|
+
encode_frame,
|
|
10
|
+
decode_frame,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"JsonRpcPeer",
|
|
15
|
+
"StdioByteStream",
|
|
16
|
+
"ConnectionClosedError",
|
|
17
|
+
"RpcError",
|
|
18
|
+
"encode_frame",
|
|
19
|
+
"decode_frame",
|
|
20
|
+
]
|