175 lines
5.7 KiB
Python
175 lines
5.7 KiB
Python
# scopes.core.core
|
|
|
|
"""A concurrent cybernetic communication system"""
|
|
|
|
from __future__ import annotations # no check of type annotations at runtime
|
|
import asyncio
|
|
from collections.abc import Awaitable, Callable, Coroutine
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Self
|
|
|
|
from scopes.core import actor
|
|
from scopes.core.message import Message
|
|
from scopes import util
|
|
from scopes.util import keywords as k, Symbol, Symbols
|
|
|
|
# keywords (domains, actions, classes, stages, data keys):
|
|
k('csys env _meta',
|
|
'connect create created data default query set value',
|
|
'c00 e00 s00 op rcvr',
|
|
'initial active retired',
|
|
'address pred succ')
|
|
|
|
type Categ = tuple[Symbol, Symbol]
|
|
type Proc = Callable[[Message, Scope], Awaitable[Scope | None]]
|
|
type Program = Callable[[Scope], Proc]
|
|
type ProgramSpec = dict[tuple[Symbol, Symbol], Proc]
|
|
type Syn = Callable[[Message], Awaitable[Symbol | None]]
|
|
type SynOp = Callable[[Message], Message | None]
|
|
|
|
type Handler = Callable[[Message, Scope],
|
|
Awaitable[Message | None | tuple[Message | None, Scope | None]]]
|
|
type Handlers = Handler | None | list[Handlers]
|
|
type Actions = dict[Symbol, Handlers]
|
|
|
|
def neuron(scope: Scope) -> actor.Mailbox:
|
|
async def process(msg: Message) -> actor.Behavior | None:
|
|
nonlocal scope
|
|
scope = await scope.proc(msg, scope) or scope
|
|
return process
|
|
return actor.create(process)
|
|
|
|
def synapse(rcvr: actor.Mailbox, op: SynOp = lambda x: x) -> Syn:
|
|
async def syn(msg: Message) -> Symbol | None:
|
|
msg_new = op(msg)
|
|
if msg_new is not None:
|
|
return await actor.send(rcvr, msg_new)
|
|
return None
|
|
return syn
|
|
|
|
env_dummy: actor.Mailbox = actor.Mailbox()
|
|
|
|
def notify() -> Handler:
|
|
async def handle(msg: Message, scope: Scope) -> None:
|
|
await actor.send(scope.environ, msg)
|
|
return handle
|
|
|
|
def gen_proc(actions: Actions, default: Handlers = notify(),
|
|
prepare: Handlers = None, finalize: Handlers = None) -> Proc:
|
|
async def proc(msg: Message, scope: Scope) -> Scope:
|
|
acts = actions.get(msg.action, actions.get(k.default, default))
|
|
acts = util.flatten(prepare, acts, finalize, ignore_none=True)
|
|
for act in acts:
|
|
msg = await act(msg, scope)
|
|
if isinstance(msg, tuple):
|
|
msg, new_scope = msg
|
|
if new_scope is not None:
|
|
scope = new_scope
|
|
if msg is None:
|
|
break
|
|
return scope
|
|
return proc
|
|
|
|
def gen_program(spec: ProgramSpec, default: Proc = gen_proc({})) -> Program:
|
|
def program(scope: Scope) -> Proc:
|
|
key = (scope.categ[1], scope.stage)
|
|
proc = spec.get(key, spec.get((key[0], k.default)))
|
|
if proc is None:
|
|
proc = spec.get((k.default, key[1]), default)
|
|
return proc
|
|
return program
|
|
|
|
@dataclass
|
|
class Scope:
|
|
|
|
value: Any = 0
|
|
categ: Categ = (k.csys, k.c00)
|
|
stage: Symbol = k.initial
|
|
proc: Proc = field(init=False)
|
|
program: Program = gen_program({})
|
|
syns: list[Syn | None] = field(default_factory=list)
|
|
environ: actor.Mailbox = env_dummy
|
|
|
|
def __post_init__(self):
|
|
self.set_proc()
|
|
|
|
def set_proc(self) -> Self:
|
|
self.proc = self.program(self)
|
|
return self
|
|
|
|
def create_scope(env: actor.Mailbox, cat: Categ = (k.csys, k.c00), **kw) -> Scope:
|
|
return Scope(environ=env, categ=cat, **kw)
|
|
|
|
def clone_scope(scope: Scope, **kw) -> Scope:
|
|
value = kw.get('value', 0)
|
|
categ = kw.get('categ', scope.categ)
|
|
stage = kw.get('stage', k.initial)
|
|
return Scope(environ=scope.environ, value=value, categ=categ, stage=stage,
|
|
program=scope.program, syns=[])
|
|
|
|
async def create_zero(env: actor.Mailbox) -> actor.Mailbox:
|
|
scope = create_scope(env, program=gen_program({}, default=std_proc()))
|
|
zero = neuron(scope)
|
|
await notify_created(Message(k.csys), zero, scope)
|
|
return zero
|
|
|
|
# predefined action handlers and Action sets
|
|
|
|
def noop(no_forward: bool = False) -> Handler:
|
|
async def handle(msg: Message, scope: Scope) -> Message | None:
|
|
return None if no_forward else msg
|
|
return handle
|
|
|
|
def forward(no_syns_notify: bool = False) -> Handler:
|
|
do_notify = notify() if no_syns_notify else None
|
|
async def handle(msg: Message, scope: Scope) -> None:
|
|
count = 0
|
|
for idx, syn in enumerate(scope.syns):
|
|
res = await syn(msg) if syn is not None else None
|
|
match res:
|
|
case k.retired: scope.syns[idx] = None
|
|
case k.ok: count += 1
|
|
if count == 0 and do_notify:
|
|
do_notify(msg, scope)
|
|
return handle
|
|
|
|
def value_add() -> Handler:
|
|
async def handle(msg: Message, scope: Scope) -> tuple[Message, Scope]:
|
|
return msg, scope
|
|
return handle
|
|
|
|
def create() -> Handler:
|
|
async def handle(msg: Message, scope: Scope) -> None:
|
|
new = neuron(clone_scope(scope, **msg.data))
|
|
await notify_created(msg, new, scope)
|
|
conn = msg.data.get(k.connect)
|
|
return handle
|
|
|
|
def connect() -> Handler:
|
|
async def handle(msg: Message, scope: Scope) -> None:
|
|
return None
|
|
return handle
|
|
|
|
def basic_actions() -> Actions:
|
|
acts: Actions = {
|
|
k.value: value_add(),
|
|
k.create: create(),
|
|
k.connect: connect()
|
|
}
|
|
return acts
|
|
|
|
# predefined specialized procs
|
|
|
|
def std_proc(actions: Actions | None = None, finalize: Handler = forward(),
|
|
more: Actions = {}) -> Proc:
|
|
if actions is None:
|
|
actions = basic_actions()
|
|
actions.update(more)
|
|
return gen_proc(actions, finalize=finalize)
|
|
|
|
# helpers
|
|
|
|
async def notify_created(msg: Message, new: actor.Mailbox, scope: Scope) -> None:
|
|
msg = Message(msg.domain, k.created, data=msg.data | {k.new: new})
|
|
await notify()(msg, scope)
|
|
|