diff --git a/phasmplatform/common/container.py b/phasmplatform/common/container.py new file mode 100644 index 0000000..07985dc --- /dev/null +++ b/phasmplatform/common/container.py @@ -0,0 +1,15 @@ +from typing import List + +from .image import Image +from .method import Method + + +class Container: + __slots__ = ('image', 'methods', ) + + image: Image + methods: List[Method] + + def __init__(self, image: Image, methods: List[Method]) -> None: + self.image = image + self.methods = methods diff --git a/phasmplatform/common/image.py b/phasmplatform/common/image.py new file mode 100644 index 0000000..2452d1a --- /dev/null +++ b/phasmplatform/common/image.py @@ -0,0 +1,7 @@ +class Image: + __slots__ = ('hash', ) + + hash: str + + def __init__(self, hash: str) -> None: + self.hash = hash diff --git a/phasmplatform/common/method.py b/phasmplatform/common/method.py new file mode 100644 index 0000000..de2d198 --- /dev/null +++ b/phasmplatform/common/method.py @@ -0,0 +1,28 @@ +from typing import List + + +from .valuetype import ValueType + + +class MethodArgument: + __slots__ = ('name', 'value_type', ) + + name: str + value_type: ValueType + + def __init__(self, name: str, value_type: ValueType) -> None: + self.name = name + self.value_type = value_type + + +class Method: + __slots__ = ('name', 'args', 'return_type', ) + + name: str + args: List[MethodArgument] + return_type: ValueType + + def __init__(self, name: str, args: List[MethodArgument], return_type: ValueType) -> None: + self.name = name + self.args = args + self.return_type = return_type diff --git a/phasmplatform/common/value.py b/phasmplatform/common/value.py index 1ba44ea..61a59c9 100644 --- a/phasmplatform/common/value.py +++ b/phasmplatform/common/value.py @@ -1,26 +1,22 @@ -from typing import Any, Generic, TypeVar +from typing import Any, Union -T = TypeVar('T') +from .valuetype import ValueType + +ValueData = Union[None, bytes] -class BaseValue(Generic[T]): - __slots__ = ('data', ) +class Value: + __slots__ = ('value_type', 'data', ) - data: T + value_type: ValueType + data: ValueData - def __init__(self, data: T) -> None: + def __init__(self, value_type: ValueType, data: ValueData) -> None: + self.value_type = value_type self.data = data def __eq__(self, other: Any) -> bool: - return self.__class__ is other.__class__ and self.data == other.data + return self.value_type is other.value_type and self.data == other.data def __repr__(self) -> str: - return f'{self.__class__.__name__}({repr(self.data)})' - - -class UntypedValue(BaseValue[Any]): - pass - - -class BytesValue(BaseValue[bytes]): - pass + return f'Value(valuetype.{self.value_type.name}, {repr(self.data)})' diff --git a/phasmplatform/common/valuetype.py b/phasmplatform/common/valuetype.py new file mode 100644 index 0000000..8d01684 --- /dev/null +++ b/phasmplatform/common/valuetype.py @@ -0,0 +1,12 @@ +class ValueType: + __slots__ = ('name', ) + + name: str + + def __init__(self, name: str) -> None: + self.name = name + + +bytes = ValueType('bytes') + +none = ValueType('none') diff --git a/phasmplatform/worker/__main__.py b/phasmplatform/worker/__main__.py index 5d4e366..5dc30ae 100644 --- a/phasmplatform/worker/__main__.py +++ b/phasmplatform/worker/__main__.py @@ -1,15 +1,28 @@ -from typing import Any - import sys +from phasmplatform.common import valuetype from phasmplatform.common.config import from_toml +from phasmplatform.common.method import Method, MethodArgument from phasmplatform.common.router import StdOutRouter -from phasmplatform.common.value import BaseValue, BytesValue +from phasmplatform.common.value import Value -from .runners.base import BaseRunner +from .runners.base import RunnerInterface from .runners.wasmtime import WasmTimeRunner +def somefunc(runner: RunnerInterface) -> None: + inp = Value(valuetype.bytes, b'Hello, world!') + print('inp', inp) + + def on_respond(out: Value) -> None: + print('out', out) + assert out == inp + + echo = Method('echo', [MethodArgument('msg', valuetype.bytes)], valuetype.bytes) + + runner.do_call(echo, [inp], on_respond) + + def main() -> int: with open('config.toml', 'rb') as fil: config = from_toml(fil) @@ -18,26 +31,17 @@ def main() -> int: stdout_router = StdOutRouter() - foo: BaseRunner - with open('/home/johan/projects/idea/phasm/examples/platform.wasm', 'rb') as fil: foo = WasmTimeRunner(stdout_router, fil.read()) - namespace = b'test-namespace' - topic = b'test-topic' - kind = b'test-kind' - body = b'test-body' + # namespace = b'test-namespace' + # topic = b'test-topic' + # kind = b'test-kind' + # body = b'test-body' - foo.handle_message(namespace, topic, kind, body) + # foo.handle_message(namespace, topic, kind, body) - inp = BytesValue(b'Hello, world!') - print('inp', inp) - - def on_respond(out: BaseValue[Any]) -> None: - print('out', out) - assert out == inp - - foo.do_call('echo', [inp], on_respond) + somefunc(foo) return 0 diff --git a/phasmplatform/worker/runners/base.py b/phasmplatform/worker/runners/base.py index 02199b4..dd20aa9 100644 --- a/phasmplatform/worker/runners/base.py +++ b/phasmplatform/worker/runners/base.py @@ -1,9 +1,18 @@ -from typing import TextIO +from typing import Callable, List, TextIO from phasmplatform.common.router import BaseRouter +from phasmplatform.common.method import Method +from phasmplatform.common.value import Value -class BaseRunner: +class RunnerInterface: + __slots__ = ('router', ) + + def do_call(self, method: Method, args: List[Value], on_result: Callable[[Value], None]) -> None: + raise NotImplementedError + + +class BaseRunner(RunnerInterface): __slots__ = ('router', ) router: BaseRouter diff --git a/phasmplatform/worker/runners/wasmtime.py b/phasmplatform/worker/runners/wasmtime.py index 45de585..e3d1a41 100644 --- a/phasmplatform/worker/runners/wasmtime.py +++ b/phasmplatform/worker/runners/wasmtime.py @@ -1,12 +1,15 @@ -from typing import Any, Callable, List, Union +from typing import Callable, List, Union import ctypes import struct import wasmtime +from phasmplatform.common import valuetype +from phasmplatform.common.method import Method from phasmplatform.common.router import BaseRouter -from phasmplatform.common.value import BaseValue, BytesValue, UntypedValue +from phasmplatform.common.value import Value +from phasmplatform.common.valuetype import ValueType from .base import BaseRunner @@ -66,20 +69,39 @@ class WasmTimeRunner(BaseRunner): return raw[ptr + 4:ptr + 4 + length] - def convert_value(self, val: BaseValue[Any]) -> Union[int, float]: - if isinstance(val, BytesValue): + def value_to_wasm(self, val: Value) -> Union[None, int, float]: + if val.value_type is valuetype.bytes: + assert isinstance(val.data, bytes) # type hint return self.alloc_bytes(val.data) raise NotImplementedError(val) - def do_call(self, method_name: str, args: List[BaseValue[Any]], callback: Callable[[BaseValue[Any]], None]) -> None: - method = self.exports[method_name] - assert isinstance(method, wasmtime.Func) + def value_from_wasm(self, value_type: ValueType, val: Union[None, int, float]) -> Value: + if value_type is valuetype.bytes: + assert isinstance(val, int) # typ hint + return Value(valuetype.bytes, self.read_bytes(val)) - act_args = [self.convert_value(x) for x in args] - result = method(self.store, *act_args) + raise NotImplementedError(value_type, val) - callback(UntypedValue(result)) # TODO: This returns a bytes pointer, but we can't detect that in advance + def do_call(self, method: Method, args: List[Value], on_result: Callable[[Value], None]) -> None: + wasm_method = self.exports[method.name] + assert isinstance(wasm_method, wasmtime.Func) + + act_args = [self.value_to_wasm(x) for x in args] + result = wasm_method(self.store, *act_args) + assert result is None or isinstance(result, (int, float, )) # type hint + on_result(self.value_from_wasm(method.return_type, result)) + + # callback(UntypedValue(result)) # TODO: This returns a bytes pointer, but we can't detect that in advance + + # def do_call(self, method_name: str, args: List[BaseValue[Any]], callback: Callable[[BaseValue[Any]], None]) -> None: + # method = self.exports[method_name] + # assert isinstance(method, wasmtime.Func) + + # act_args = [self.convert_value(x) for x in args] + # result = method(self.store, *act_args) + + # callback(UntypedValue(result)) # TODO: This returns a bytes pointer, but we can't detect that in advance def handle_message(self, namespace: bytes, topic: bytes, kind: bytes, body: bytes) -> None: namespace_ptr = self.alloc_bytes(namespace)