Skip to content

Commit

Permalink
Split out RLV handling
Browse files Browse the repository at this point in the history
  • Loading branch information
SaladDais committed Dec 20, 2023
1 parent 81eae4e commit bd67d6f
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 25 deletions.
6 changes: 3 additions & 3 deletions addon_examples/deformer_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,17 +152,17 @@ def _reapply_deformer(self, session: Session, region: ProxiedRegion):
local_anim.LocalAnimAddon.apply_local_anim(session, region, "deformer_addon", anim_data)

def handle_rlv_command(self, session: Session, region: ProxiedRegion, source: UUID,
cmd: str, options: List[str], param: str):
behaviour: str, options: List[str], param: str):
# An object in-world can also tell the client how to deform itself via
# RLV-style commands.

# We only handle commands
if param != "force":
return

if cmd == "stop_deforming":
if behaviour == "stop_deforming":
self.deform_joints.clear()
elif cmd == "deform_joints":
elif behaviour == "deform_joints":
self.deform_joints.clear()
for joint_data in options:
joint_split = joint_data.split("|")
Expand Down
6 changes: 3 additions & 3 deletions addon_examples/local_anim.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,15 @@ async def _try_reload_anims(self, session: Session):
await asyncio.sleep(1.0)

def handle_rlv_command(self, session: Session, region: ProxiedRegion, source: UUID,
cmd: str, options: List[str], param: str):
behaviour: str, options: List[str], param: str):
# We only handle commands
if param != "force":
return

if cmd == "stop_local_anim":
if behaviour == "stop_local_anim":
self.apply_local_anim(session, region, options[0], new_data=None)
return True
elif cmd == "start_local_anim":
elif behaviour == "start_local_anim":
self.apply_local_anim_from_file(session, region, options[0])
return True

Expand Down
51 changes: 51 additions & 0 deletions hippolyzer/lib/client/rlv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from typing import NamedTuple, List, Sequence

from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.base.templates import ChatType


class RLVCommand(NamedTuple):
behaviour: str
param: str
options: List[str]


class RLVParser:
@staticmethod
def is_rlv_message(msg: Message) -> bool:
chat: str = msg["ChatData"]["Message"]
chat_type: int = msg["ChatData"]["ChatType"]
return chat and chat.startswith("@") and chat_type == ChatType.OWNER

@staticmethod
def parse_chat(chat: str) -> List[RLVCommand]:
assert chat.startswith("@")
chat = chat.lstrip("@")
commands = []
for command_str in chat.split(","):
if not command_str:
continue
# RLV-style command, `<cmd>(:<option1>;<option2>)?(=<param>)?`
# Roughly (?<behaviour>[^:=]+)(:(?<option>[^=]*))?=(?<param>\w+)
options, _, param = command_str.partition("=")
behaviour, _, options = options.partition(":")
# TODO: Not always correct, commands can specify their own parsing for the option field
# maybe special-case these?
options = options.split(";") if options else []
commands.append(RLVCommand(behaviour, param, options))
return commands

@staticmethod
def format_chat(commands: Sequence[RLVCommand]) -> str:
assert commands
chat = ""
for command in commands:
if chat:
chat += ","

chat += command.behaviour
if command.options:
chat += ":" + ";".join(command.options)
if command.param:
chat += "=" + command.param
return "@" + chat
2 changes: 1 addition & 1 deletion hippolyzer/lib/proxy/addon_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ def handle_circuit_created(self, session: Session, region: ProxiedRegion):
pass

def handle_rlv_command(self, session: Session, region: ProxiedRegion, source: UUID,
cmd: str, options: List[str], param: str):
behaviour: str, options: List[str], param: str):
pass

def handle_proxied_packet(self, session_manager: SessionManager, packet: UDPPacket,
Expand Down
35 changes: 17 additions & 18 deletions hippolyzer/lib/proxy/addons.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from hippolyzer.lib.base.helpers import get_mtime
from hippolyzer.lib.base.message.message import Message
from hippolyzer.lib.base.network.transport import UDPPacket
from hippolyzer.lib.client.rlv import RLVParser
from hippolyzer.lib.proxy import addon_ctx
from hippolyzer.lib.proxy.task_scheduler import TaskLifeScope, TaskScheduler

Expand Down Expand Up @@ -348,7 +349,7 @@ def _unload_module(cls, old_mod: ModuleType):
cls.SCHEDULER.kill_matching_tasks(lifetime_mask=TaskLifeScope.ADDON, creator=addon)

@classmethod
def _call_all_addon_hooks(cls, hook_name, *args, call_async=False, **kwargs):
def _call_all_addon_hooks(cls, hook_name, *args, call_async=False, **kwargs) -> Optional[bool]:
for module in cls.FRESH_ADDON_MODULES.values():
if not module:
continue
Expand Down Expand Up @@ -391,7 +392,7 @@ def _call_module_hooks(cls, module, hook_name, *args, call_async=False, **kwargs
return cls._try_call_hook(module, hook_name, *args, call_async=call_async, **kwargs)

@classmethod
def _try_call_hook(cls, addon, hook_name, *args, call_async=False, **kwargs):
def _try_call_hook(cls, addon, hook_name, *args, call_async=False, **kwargs) -> Optional[bool]:
if cls._SUBPROCESS:
return

Expand Down Expand Up @@ -452,32 +453,30 @@ def handle_lludp_message(cls, session: Session, region: ProxiedRegion, message:
raise
return True
if message.name == "ChatFromSimulator" and "ChatData" in message:
chat: str = message["ChatData"]["Message"]
chat_type: int = message["ChatData"]["ChatType"]
# RLV-style OwnerSay?
if chat and chat.startswith("@") and chat_type == 8:
if RLVParser.is_rlv_message(message):
# RLV allows putting multiple commands into one message, blindly splitting on ",".
chat = chat.lstrip("@")
all_cmds_handled = True
for command_str in chat.split(","):
if not command_str:
continue
# RLV-style command, `@<cmd>(:<option1>;<option2>)?(=<param>)?`
options, _, param = command_str.partition("=")
cmd, _, options = options.partition(":")
# TODO: Not always correct, commands can specify their own parsing for the option field
options = options.split(";") if options else []
source = message["ChatData"]["SourceID"]
chat: str = message["ChatData"]["Message"]
source = message["ChatData"]["SourceID"]
for command in RLVParser.parse_chat(chat):
try:
with addon_ctx.push(session, region):
handled = cls._call_all_addon_hooks("handle_rlv_command",
session, region, source, cmd, options, param)
handled = cls._call_all_addon_hooks(
"handle_rlv_command",
session,
region,
source,
command.behaviour,
command.options,
command.param,
)
if handled:
region.circuit.drop_message(message)
else:
all_cmds_handled = False
except:
LOG.exception(f"Failed while handling command {command_str!r}")
LOG.exception(f"Failed while handling command {command!r}")
all_cmds_handled = False
if not cls._SWALLOW_ADDON_EXCEPTIONS:
raise
Expand Down
36 changes: 36 additions & 0 deletions tests/client/test_rlv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import unittest

from hippolyzer.lib.base.message.message import Message, Block
from hippolyzer.lib.base.templates import ChatType
from hippolyzer.lib.client.rlv import RLVParser, RLVCommand


class TestRLV(unittest.TestCase):
def test_is_rlv_command(self):
msg = Message(
"ChatFromSimulator",
Block("ChatData", Message="@foobar", ChatType=ChatType.OWNER)
)
self.assertTrue(RLVParser.is_rlv_message(msg))
msg["ChatData"]["ChatType"] = ChatType.NORMAL
self.assertFalse(RLVParser.is_rlv_message(msg))

def test_rlv_parse_single_command(self):
cmd = RLVParser.parse_chat("@foo:bar;baz=quux")[0]
self.assertEqual("foo", cmd.behaviour)
self.assertListEqual(["bar", "baz"], cmd.options)
self.assertEqual("quux", cmd.param)

def test_rlv_parse_multiple_commands(self):
cmds = RLVParser.parse_chat("@foo:bar;baz=quux,bazzy")
self.assertEqual("foo", cmds[0].behaviour)
self.assertListEqual(["bar", "baz"], cmds[0].options)
self.assertEqual("quux", cmds[0].param)
self.assertEqual("bazzy", cmds[1].behaviour)

def test_rlv_format_commands(self):
chat = RLVParser.format_chat([
RLVCommand("foo", "quux", ["bar", "baz"]),
RLVCommand("bazzy", "", [])
])
self.assertEqual("@foo:bar;baz=quux,bazzy", chat)

0 comments on commit bd67d6f

Please sign in to comment.