runtimepy 5.14.2__py3-none-any.whl → 5.15.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.
Files changed (96) hide show
  1. runtimepy/__init__.py +2 -2
  2. runtimepy/channel/__init__.py +1 -4
  3. runtimepy/channel/environment/__init__.py +93 -2
  4. runtimepy/channel/environment/create.py +16 -1
  5. runtimepy/channel/environment/sample.py +10 -2
  6. runtimepy/channel/registry.py +2 -3
  7. runtimepy/codec/protocol/base.py +34 -14
  8. runtimepy/codec/protocol/json.py +5 -3
  9. runtimepy/codec/system/__init__.py +6 -2
  10. runtimepy/control/source.py +1 -1
  11. runtimepy/data/404.md +16 -0
  12. runtimepy/data/base.yaml +3 -0
  13. runtimepy/data/css/bootstrap_extra.css +59 -44
  14. runtimepy/data/css/main.css +23 -4
  15. runtimepy/data/dummy_load.yaml +2 -2
  16. runtimepy/data/factories.yaml +1 -0
  17. runtimepy/data/js/classes/App.js +54 -2
  18. runtimepy/data/js/classes/ChannelTable.js +6 -8
  19. runtimepy/data/js/classes/Plot.js +9 -4
  20. runtimepy/data/js/classes/TabFilter.js +47 -9
  21. runtimepy/data/js/classes/TabInterface.js +106 -11
  22. runtimepy/data/js/classes/WindowHashManager.js +30 -15
  23. runtimepy/data/js/init.js +18 -1
  24. runtimepy/data/js/markdown_page.js +10 -0
  25. runtimepy/data/schemas/BitFields.yaml +9 -0
  26. runtimepy/data/schemas/RuntimeEnum.yaml +6 -0
  27. runtimepy/data/schemas/StructConfig.yaml +9 -1
  28. runtimepy/data/static/css/bootstrap-icons.min.css +4 -3
  29. runtimepy/data/static/css/bootstrap.min.css +3 -4
  30. runtimepy/data/static/css/fonts/bootstrap-icons.woff +0 -0
  31. runtimepy/data/static/css/fonts/bootstrap-icons.woff2 +0 -0
  32. runtimepy/data/static/js/bootstrap.bundle.min.js +5 -4
  33. runtimepy/data/static/js/webglplot.umd.min.js +2 -1
  34. runtimepy/data/static/svg/outline-dark.svg +22 -0
  35. runtimepy/data/static/svg/outline-light.svg +22 -0
  36. runtimepy/enum/__init__.py +13 -1
  37. runtimepy/enum/registry.py +13 -1
  38. runtimepy/message/__init__.py +3 -3
  39. runtimepy/mixins/logging.py +6 -1
  40. runtimepy/net/__init__.py +0 -2
  41. runtimepy/net/arbiter/info.py +36 -4
  42. runtimepy/net/arbiter/struct/__init__.py +3 -2
  43. runtimepy/net/connection.py +4 -5
  44. runtimepy/net/html/__init__.py +29 -11
  45. runtimepy/net/html/bootstrap/__init__.py +2 -2
  46. runtimepy/net/html/bootstrap/elements.py +44 -24
  47. runtimepy/net/html/bootstrap/tabs.py +18 -11
  48. runtimepy/net/http/__init__.py +3 -3
  49. runtimepy/net/http/request_target.py +3 -3
  50. runtimepy/net/mixin.py +4 -2
  51. runtimepy/net/server/__init__.py +16 -9
  52. runtimepy/net/server/app/__init__.py +1 -0
  53. runtimepy/net/server/app/create.py +3 -3
  54. runtimepy/net/server/app/env/__init__.py +28 -4
  55. runtimepy/net/server/app/env/settings.py +4 -7
  56. runtimepy/net/server/app/env/tab/controls.py +141 -27
  57. runtimepy/net/server/app/env/tab/html.py +68 -26
  58. runtimepy/net/server/app/env/widgets.py +115 -61
  59. runtimepy/net/server/app/landing_page.py +1 -1
  60. runtimepy/net/server/html.py +2 -2
  61. runtimepy/net/server/json.py +1 -1
  62. runtimepy/net/server/markdown.py +18 -12
  63. runtimepy/net/server/mux.py +29 -0
  64. runtimepy/net/stream/__init__.py +6 -5
  65. runtimepy/net/stream/base.py +4 -2
  66. runtimepy/net/tcp/connection.py +5 -3
  67. runtimepy/net/tcp/http/__init__.py +10 -9
  68. runtimepy/net/tcp/protocol.py +2 -2
  69. runtimepy/net/tcp/scpi/__init__.py +5 -2
  70. runtimepy/net/tcp/telnet/__init__.py +2 -1
  71. runtimepy/net/udp/connection.py +10 -6
  72. runtimepy/net/udp/protocol.py +5 -6
  73. runtimepy/net/udp/queue.py +5 -2
  74. runtimepy/net/udp/tftp/base.py +2 -1
  75. runtimepy/net/websocket/connection.py +50 -8
  76. runtimepy/primitives/array/__init__.py +7 -5
  77. runtimepy/primitives/base.py +3 -2
  78. runtimepy/primitives/field/__init__.py +35 -2
  79. runtimepy/primitives/field/fields.py +11 -2
  80. runtimepy/primitives/field/manager/base.py +19 -2
  81. runtimepy/primitives/serializable/base.py +5 -2
  82. runtimepy/primitives/serializable/fixed.py +5 -2
  83. runtimepy/primitives/serializable/prefixed.py +4 -1
  84. runtimepy/primitives/types/base.py +4 -1
  85. runtimepy/primitives/types/bounds.py +10 -4
  86. runtimepy/registry/__init__.py +20 -0
  87. runtimepy/registry/name.py +6 -0
  88. runtimepy/requirements.txt +2 -2
  89. runtimepy/ui/controls.py +20 -1
  90. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/METADATA +6 -6
  91. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/RECORD +95 -92
  92. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/WHEEL +1 -1
  93. runtimepy/data/404.html +0 -7
  94. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/entry_points.txt +0 -0
  95. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/licenses/LICENSE +0 -0
  96. {runtimepy-5.14.2.dist-info → runtimepy-5.15.0.dist-info}/top_level.txt +0 -0
@@ -12,9 +12,11 @@ from typing import Any as _Any
12
12
  from typing import Optional as _Optional
13
13
  from typing import TypeVar as _TypeVar
14
14
 
15
+ # third-party
16
+ from vcorelib.io import BinaryMessage
17
+
15
18
  # internal
16
19
  from runtimepy.net import IpHost, get_free_socket, normalize_host
17
- from runtimepy.net.connection import BinaryMessage as _BinaryMessage
18
20
  from runtimepy.net.connection import Connection as _Connection
19
21
  from runtimepy.net.connection import EchoConnection as _EchoConnection
20
22
  from runtimepy.net.connection import NullConnection as _NullConnection
@@ -86,11 +88,13 @@ class UdpConnection(_Connection, _TransportMixin):
86
88
 
87
89
  @_abstractmethod
88
90
  async def process_datagram(
89
- self, data: bytes, addr: tuple[str, int]
91
+ self, data: BinaryMessage, addr: tuple[str, int]
90
92
  ) -> bool:
91
93
  """Process a datagram."""
92
94
 
93
- def sendto(self, data: bytes, addr: IpHostTuplelike = None) -> None:
95
+ def sendto(
96
+ self, data: BinaryMessage, addr: IpHostTuplelike = None
97
+ ) -> None:
94
98
  """Send to a specific address."""
95
99
 
96
100
  try:
@@ -108,7 +112,7 @@ class UdpConnection(_Connection, _TransportMixin):
108
112
  """Enqueue a text message to send."""
109
113
  self.sendto(data.encode(), addr=self.remote_address)
110
114
 
111
- def send_binary(self, data: _BinaryMessage) -> None:
115
+ def send_binary(self, data: BinaryMessage) -> None:
112
116
  """Enqueue a binary message to send."""
113
117
  self.sendto(data, addr=self.remote_address)
114
118
 
@@ -216,7 +220,7 @@ class EchoUdpConnection(UdpConnection, _EchoConnection):
216
220
  """An echo connection for UDP."""
217
221
 
218
222
  async def process_datagram(
219
- self, data: bytes, addr: tuple[str, int]
223
+ self, data: BinaryMessage, addr: tuple[str, int]
220
224
  ) -> bool:
221
225
  """Process a datagram."""
222
226
 
@@ -228,7 +232,7 @@ class NullUdpConnection(UdpConnection, _NullConnection):
228
232
  """A null UDP connection."""
229
233
 
230
234
  async def process_datagram(
231
- self, data: bytes, addr: tuple[str, int]
235
+ self, data: BinaryMessage, addr: tuple[str, int]
232
236
  ) -> bool:
233
237
  """Process a datagram."""
234
238
  return True
@@ -6,14 +6,13 @@ A module implementing a DatagramProtocol for UdpConnection.
6
6
  import asyncio as _asyncio
7
7
  from asyncio import DatagramProtocol as _DatagramProtocol
8
8
  import logging
9
- from typing import Tuple as _Tuple
10
9
 
11
10
  # third-party
11
+ from vcorelib.io import BinaryMessage
12
12
  from vcorelib.logging import LoggerMixin, LoggerType
13
13
  from vcorelib.math import RateLimiter
14
14
 
15
15
  # internal
16
- from runtimepy.net.connection import BinaryMessage as _BinaryMessage
17
16
  from runtimepy.net.connection import Connection as _Connection
18
17
 
19
18
 
@@ -26,13 +25,13 @@ class UdpQueueProtocol(_DatagramProtocol):
26
25
  def __init__(self) -> None:
27
26
  """Initialize this protocol."""
28
27
 
29
- self.queue: _asyncio.Queue[
30
- _Tuple[_BinaryMessage, _Tuple[str, int]]
31
- ] = _asyncio.Queue()
28
+ self.queue: _asyncio.Queue[tuple[BinaryMessage, tuple[str, int]]] = (
29
+ _asyncio.Queue()
30
+ )
32
31
 
33
32
  self.log_limiter = RateLimiter.from_s(1.0)
34
33
 
35
- def datagram_received(self, data: bytes, addr: _Tuple[str, int]) -> None:
34
+ def datagram_received(self, data: bytes, addr: tuple[str, int]) -> None:
36
35
  """Handle incoming data."""
37
36
  self.queue.put_nowait((data, addr))
38
37
 
@@ -5,10 +5,13 @@ A module implementing a simple queue-based UDP interface.
5
5
  # built-in
6
6
  from asyncio import Queue
7
7
 
8
+ # third-party
9
+ from vcorelib.io import BinaryMessage
10
+
8
11
  # internal
9
12
  from runtimepy.net.udp.connection import UdpConnection
10
13
 
11
- DatagramQueue = Queue[tuple[bytes, tuple[str, int]]]
14
+ DatagramQueue = Queue[tuple[BinaryMessage, tuple[str, int]]]
12
15
 
13
16
 
14
17
  class QueueUdpConnection(UdpConnection):
@@ -21,7 +24,7 @@ class QueueUdpConnection(UdpConnection):
21
24
  self.datagrams = Queue()
22
25
 
23
26
  async def process_datagram(
24
- self, data: bytes, addr: tuple[str, int]
27
+ self, data: BinaryMessage, addr: tuple[str, int]
25
28
  ) -> bool:
26
29
  """Process a datagram."""
27
30
  self.datagrams.put_nowait((data, addr))
@@ -11,6 +11,7 @@ from pathlib import Path
11
11
  from typing import BinaryIO, Callable
12
12
 
13
13
  # third-party
14
+ from vcorelib.io import BinaryMessage
14
15
  from vcorelib.math import metrics_time_ns
15
16
 
16
17
  # internal
@@ -353,7 +354,7 @@ class BaseTftpConnection(UdpConnection):
353
354
  self.endpoint(addr).handle_error(error_code, message)
354
355
 
355
356
  async def process_datagram(
356
- self, data: bytes, addr: tuple[str, int]
357
+ self, data: BinaryMessage, addr: tuple[str, int]
357
358
  ) -> bool:
358
359
  """Process a datagram."""
359
360
 
@@ -20,6 +20,7 @@ from typing import Union as _Union
20
20
 
21
21
  # third-party
22
22
  from vcorelib.asyncio import log_exceptions as _log_exceptions
23
+ from vcorelib.io import BinaryMessage
23
24
  import websockets
24
25
  from websockets.asyncio.client import ClientConnection as _ClientConnection
25
26
  from websockets.asyncio.server import Server as _Server
@@ -29,7 +30,7 @@ from websockets.exceptions import ConnectionClosed as _ConnectionClosed
29
30
 
30
31
  # internal
31
32
  from runtimepy.net import sockname as _sockname
32
- from runtimepy.net.connection import BinaryMessage, Connection
33
+ from runtimepy.net.connection import Connection
33
34
  from runtimepy.net.connection import EchoConnection as _EchoConnection
34
35
  from runtimepy.net.connection import NullConnection as _NullConnection
35
36
  from runtimepy.net.manager import ConnectionManager as _ConnectionManager
@@ -41,6 +42,17 @@ V = _TypeVar("V")
41
42
  LOG = _getLogger(__name__)
42
43
 
43
44
 
45
+ async def websocket_connect(uri: str, **kwargs) -> _ClientConnection:
46
+ """Attempt to connect a websocket interface."""
47
+
48
+ # Defaults.
49
+ kwargs.setdefault("use_ssl", uri.startswith("wss"))
50
+
51
+ return await getattr(websockets, "connect")( # type: ignore
52
+ uri, **handle_possible_ssl(**kwargs)
53
+ )
54
+
55
+
44
56
  class WebsocketConnection(Connection):
45
57
  """A simple websocket connection interface."""
46
58
 
@@ -54,6 +66,10 @@ class WebsocketConnection(Connection):
54
66
  self.protocol = protocol
55
67
  super().__init__(self.protocol.logger, **kwargs)
56
68
 
69
+ # Store connection-instantiation arguments (for connection restarting).
70
+ self._uri: str = ""
71
+ self._conn_kwargs: dict[str, _Any] = {}
72
+
57
73
  async def _handle_connection_closed(
58
74
  self, task: _Awaitable[V]
59
75
  ) -> _Optional[V]:
@@ -81,7 +97,9 @@ class WebsocketConnection(Connection):
81
97
 
82
98
  async def _send_binay_message(self, data: BinaryMessage) -> None:
83
99
  """Send a binary message."""
84
- await self._handle_connection_closed(self.protocol.send(data))
100
+ await self._handle_connection_closed(
101
+ self.protocol.send(data), # type: ignore
102
+ )
85
103
  self.metrics.tx.increment(len(data))
86
104
 
87
105
  async def close(self) -> None:
@@ -94,12 +112,30 @@ class WebsocketConnection(Connection):
94
112
  ) -> T:
95
113
  """Connect a client to an endpoint."""
96
114
 
97
- kwargs.setdefault("use_ssl", uri.startswith("wss"))
115
+ inst = cls(await websocket_connect(uri, **kwargs), markdown=markdown)
98
116
 
99
- protocol = await getattr(websockets, "connect")(
100
- uri, **handle_possible_ssl(**kwargs)
101
- )
102
- return cls(protocol, markdown=markdown)
117
+ # Stored for connection restart capability.
118
+ inst._uri = uri
119
+ inst._conn_kwargs = {**kwargs}
120
+
121
+ return inst
122
+
123
+ async def restart(self) -> bool:
124
+ """
125
+ Reset necessary underlying state for this connection to 'process'
126
+ again.
127
+ """
128
+
129
+ result = False
130
+
131
+ if self._uri:
132
+ with _suppress(TimeoutError, OSError):
133
+ self.protocol = await websocket_connect(
134
+ self._uri, **self._conn_kwargs
135
+ )
136
+ result = True
137
+
138
+ return result
103
139
 
104
140
  @classmethod
105
141
  @_asynccontextmanager
@@ -113,7 +149,13 @@ class WebsocketConnection(Connection):
113
149
  async with getattr(websockets, "connect")(
114
150
  uri, **handle_possible_ssl(**kwargs)
115
151
  ) as protocol:
116
- yield cls(protocol, markdown=markdown)
152
+ inst = cls(protocol, markdown=markdown)
153
+
154
+ # Stored for connection restart capability.
155
+ inst._uri = uri
156
+ inst._conn_kwargs = {**kwargs}
157
+
158
+ yield inst
117
159
 
118
160
  @classmethod
119
161
  def server_handler(
@@ -9,6 +9,9 @@ from struct import unpack as _unpack
9
9
  from typing import NamedTuple
10
10
  from typing import cast as _cast
11
11
 
12
+ # third-party
13
+ from vcorelib.io import BinaryMessage
14
+
12
15
  # internal
13
16
  from runtimepy.primitives import AnyPrimitive as _AnyPrimitive
14
17
  from runtimepy.primitives import Primitivelike as _Primitivelike
@@ -42,8 +45,6 @@ class PrimitiveArray(Serializable):
42
45
  """Initialize this primitive array."""
43
46
 
44
47
  self._primitives: list[_AnyPrimitive] = []
45
- self.byte_order = byte_order
46
- self._format: str = self.byte_order.fmt
47
48
 
48
49
  # Keep track of a quick lookup for converting between element indices
49
50
  # and byte indices.
@@ -53,12 +54,13 @@ class PrimitiveArray(Serializable):
53
54
  self.size = 0
54
55
  self.chain = None
55
56
 
57
+ super().__init__(byte_order=byte_order, chain=chain)
58
+ self._format: str = self.byte_order.fmt
59
+
56
60
  # Add initial items.
57
61
  for item in primitives:
58
62
  self.add(item)
59
63
 
60
- super().__init__(byte_order=self.byte_order, chain=chain)
61
-
62
64
  self._fragments: list["PrimitiveArray"] = []
63
65
  self._fragment_specs: list[ArrayFragmentSpec] = []
64
66
 
@@ -236,7 +238,7 @@ class PrimitiveArray(Serializable):
236
238
  """Get bytes from a fragment."""
237
239
  return bytes(self._fragments[index])
238
240
 
239
- def update(self, data: bytes, timestamp_ns: int = None) -> int:
241
+ def update(self, data: BinaryMessage, timestamp_ns: int = None) -> int:
240
242
  """Update primitive values from a bytes instance."""
241
243
 
242
244
  for primitive, item in zip(
@@ -13,6 +13,7 @@ from typing import Iterator as _Iterator
13
13
  from typing import TypeVar as _TypeVar
14
14
 
15
15
  # third-party
16
+ from vcorelib.io import BinaryMessage
16
17
  from vcorelib.math import default_time_ns, nano_str
17
18
  from vcorelib.math.keeper import TimeSource
18
19
 
@@ -243,7 +244,7 @@ class Primitive(_Generic[T]):
243
244
  stream.write(self.binary(byte_order=byte_order))
244
245
  return self.kind.size
245
246
 
246
- def update(self, data: bytes, byte_order: _ByteOrder = None) -> T:
247
+ def update(self, data: BinaryMessage, byte_order: _ByteOrder = None) -> T:
247
248
  """Update this primitive from a bytes object."""
248
249
 
249
250
  if byte_order is None:
@@ -269,7 +270,7 @@ class Primitive(_Generic[T]):
269
270
  return cls.kind.encode(value, byte_order=byte_order)
270
271
 
271
272
  @classmethod
272
- def decode(cls, data: bytes, byte_order: _ByteOrder = None) -> T:
273
+ def decode(cls, data: BinaryMessage, byte_order: _ByteOrder = None) -> T:
273
274
  """Decode a primitive of this type from provided data."""
274
275
 
275
276
  if byte_order is None:
@@ -3,6 +3,7 @@ A module implementing bit flags and fields.
3
3
  """
4
4
 
5
5
  # built-in
6
+ from typing import Optional as _Optional
6
7
  from typing import cast as _cast
7
8
 
8
9
  # third-party
@@ -14,6 +15,12 @@ from runtimepy.mixins.regex import CHANNEL_PATTERN as _CHANNEL_PATTERN
14
15
  from runtimepy.mixins.regex import RegexMixin as _RegexMixin
15
16
  from runtimepy.primitives.int import UnsignedInt as _UnsignedInt
16
17
  from runtimepy.registry.name import RegistryKey as _RegistryKey
18
+ from runtimepy.ui.controls import (
19
+ Controls,
20
+ Controlslike,
21
+ bit_slider,
22
+ normalize_controls,
23
+ )
17
24
 
18
25
 
19
26
  class BitFieldBase:
@@ -26,6 +33,8 @@ class BitFieldBase:
26
33
  width: int,
27
34
  commandable: bool = False,
28
35
  description: str = None,
36
+ controls: Controlslike = None,
37
+ default: _Optional[int | bool | str] = None,
29
38
  ) -> None:
30
39
  """Initialize this bit-field."""
31
40
 
@@ -34,6 +43,11 @@ class BitFieldBase:
34
43
  self.commandable = commandable
35
44
  self.description = description
36
45
 
46
+ if controls:
47
+ controls = normalize_controls(controls)
48
+ self.controls: _Optional[Controls] = controls # type: ignore
49
+ self.default = default
50
+
37
51
  # Compute a bit-mask for this field.
38
52
  self.width = width
39
53
  self.mask = (2**self.width) - 1
@@ -46,7 +60,7 @@ class BitFieldBase:
46
60
 
47
61
  result = ""
48
62
 
49
- for idx in range(self.raw.size * 8):
63
+ for idx in reversed(range(self.raw.size * 8)):
50
64
  val = 1 << idx
51
65
  if val & self.shifted_mask:
52
66
  result += "^"
@@ -96,11 +110,17 @@ class BitField(BitFieldBase, _RegexMixin, _EnumMixin):
96
110
  enum: _RegistryKey = None,
97
111
  commandable: bool = False,
98
112
  description: str = None,
113
+ **kwargs,
99
114
  ) -> None:
100
115
  """Initialize this bit-field."""
101
116
 
102
117
  super().__init__(
103
- raw, index, width, commandable=commandable, description=description
118
+ raw,
119
+ index,
120
+ width,
121
+ commandable=commandable,
122
+ description=description,
123
+ **kwargs,
104
124
  )
105
125
 
106
126
  # Verify bit-field parameters.
@@ -118,6 +138,13 @@ class BitField(BitFieldBase, _RegexMixin, _EnumMixin):
118
138
  self.name = name
119
139
  self._enum = enum
120
140
 
141
+ def set_slider(self) -> "BitField":
142
+ """Set slider controls for this bit field."""
143
+
144
+ assert self.controls is None
145
+ self.controls = bit_slider(self.width, False)
146
+ return self
147
+
121
148
  def asdict(self) -> _JsonObject:
122
149
  """Get this field as a dictionary."""
123
150
 
@@ -131,6 +158,10 @@ class BitField(BitFieldBase, _RegexMixin, _EnumMixin):
131
158
  result["enum"] = self.enum
132
159
  if self.commandable:
133
160
  result["commandable"] = True
161
+ if self.controls:
162
+ result["controls"] = self.controls # type: ignore
163
+ if self.default is not None:
164
+ result["default"] = self.default
134
165
  return result
135
166
 
136
167
 
@@ -145,6 +176,7 @@ class BitFlag(BitField):
145
176
  enum: _RegistryKey = None,
146
177
  commandable: bool = False,
147
178
  description: str = None,
179
+ **kwargs,
148
180
  ) -> None:
149
181
  """Initialize this bit flag."""
150
182
 
@@ -156,6 +188,7 @@ class BitFlag(BitField):
156
188
  enum=enum,
157
189
  commandable=commandable,
158
190
  description=description,
191
+ **kwargs,
159
192
  )
160
193
 
161
194
  def clear(self) -> None:
@@ -73,6 +73,8 @@ class BitFields(_RuntimepyDictCodec):
73
73
  enum=enum,
74
74
  description=desc,
75
75
  commandable=commandable,
76
+ controls=item.get("controls"),
77
+ default=item.get("default"),
76
78
  )
77
79
  flag(value)
78
80
  item["index"] = flag.index
@@ -84,6 +86,8 @@ class BitFields(_RuntimepyDictCodec):
84
86
  enum=enum,
85
87
  description=desc,
86
88
  commandable=commandable,
89
+ controls=item.get("controls"),
90
+ default=item.get("default"),
87
91
  )
88
92
  field(value)
89
93
  item["index"] = field.index
@@ -229,10 +233,15 @@ class BitFields(_RuntimepyDictCodec):
229
233
 
230
234
  @classmethod
231
235
  def new(
232
- cls: type[T], value: _Primitivelike | _AnyPrimitive = "uint8"
236
+ cls: type[T], value: _Primitivelike | _AnyPrimitive = "uint8", **kwargs
233
237
  ) -> T:
234
238
  """Create a new bit-field storage entity."""
235
239
 
236
240
  return cls.create(
237
- {"type": _cast(str, value), "fields": [], "finalized": False}
241
+ {
242
+ "type": _cast(str, value),
243
+ "fields": [],
244
+ "finalized": False,
245
+ **kwargs,
246
+ }
238
247
  )
@@ -14,6 +14,7 @@ from typing import cast as _cast
14
14
  from vcorelib.io import ARBITER as _ARBITER
15
15
  from vcorelib.io.types import EncodeResult as _EncodeResult
16
16
  from vcorelib.io.types import JsonObject as _JsonObject
17
+ from vcorelib.namespace import Namespace
17
18
  from vcorelib.paths import Pathlike as _Pathlike
18
19
 
19
20
  # internal
@@ -86,7 +87,12 @@ class BitFieldsManagerBase:
86
87
  """Encode this bit-fields manager to a file."""
87
88
  return fields_to_file(path, self.fields, **kwargs)
88
89
 
89
- def add(self, fields: _BitFields) -> int:
90
+ def add(
91
+ self,
92
+ fields: _BitFields,
93
+ namespace: Namespace = None,
94
+ track: bool = False,
95
+ ) -> int:
90
96
  """Add new bit-fields to manage."""
91
97
 
92
98
  # Ensure that new fields can't be added after the current fields
@@ -97,7 +103,12 @@ class BitFieldsManagerBase:
97
103
  self.fields.append(fields)
98
104
 
99
105
  # Register fields into the lookup structure.
106
+ to_add = {}
100
107
  for name, field in fields.fields.items():
108
+ if namespace is not None:
109
+ name = namespace.namespace(name=name, track=track)
110
+ to_add[name] = field
111
+
101
112
  ident = self.registry.register_name(name)
102
113
  assert ident is not None, "Couldn't register bit-field '{name}'!"
103
114
  assert name not in self.lookup, name
@@ -105,7 +116,13 @@ class BitFieldsManagerBase:
105
116
 
106
117
  # Also store the enum mapping.
107
118
  if field.is_enum:
108
- self.enum_lookup[name] = self.enums[field.enum]
119
+ runtime = self.enums[field.enum]
120
+ self.enum_lookup[name] = runtime
121
+ if runtime.default:
122
+ self.set(name, runtime.default)
123
+
124
+ # Add possible namespaced-name mappings.
125
+ fields.fields.update(to_add)
109
126
 
110
127
  return index
111
128
 
@@ -11,6 +11,7 @@ from typing import TypeVar
11
11
 
12
12
  # third-party
13
13
  from vcorelib import DEFAULT_ENCODING
14
+ from vcorelib.io import BinaryMessage
14
15
 
15
16
  # internal
16
17
  from runtimepy.primitives.byte_order import (
@@ -133,7 +134,7 @@ class Serializable(ABC):
133
134
  )
134
135
 
135
136
  @abstractmethod
136
- def update(self, data: bytes, timestamp_ns: int = None) -> int:
137
+ def update(self, data: BinaryMessage, timestamp_ns: int = None) -> int:
137
138
  """Update this serializable from a bytes instance."""
138
139
 
139
140
  def update_str(self, data: str, timestamp_ns: int = None) -> int:
@@ -158,7 +159,9 @@ class Serializable(ABC):
158
159
 
159
160
  return result
160
161
 
161
- def update_chain(self, data: bytes, timestamp_ns: int = None) -> int:
162
+ def update_chain(
163
+ self, data: BinaryMessage, timestamp_ns: int = None
164
+ ) -> int:
162
165
  """Update this serializable from a bytes instance."""
163
166
 
164
167
  with _BytesIO(data) as stream:
@@ -5,6 +5,9 @@ A module implementing a fixed-size bytes serializable.
5
5
  # built-in
6
6
  from copy import copy as _copy
7
7
 
8
+ # third-party
9
+ from vcorelib.io import BinaryMessage
10
+
8
11
  # internal
9
12
  from runtimepy.primitives.serializable.base import Serializable
10
13
 
@@ -31,11 +34,11 @@ class FixedChunk(Serializable):
31
34
  """Get this serializable as a bytes instance."""
32
35
  return self.data
33
36
 
34
- def update(self, data: bytes, timestamp_ns: int = None) -> int:
37
+ def update(self, data: BinaryMessage, timestamp_ns: int = None) -> int:
35
38
  """Update this serializable from a bytes instance."""
36
39
 
37
40
  del timestamp_ns
38
41
 
39
- self.data = data
42
+ self.data = bytes(data)
40
43
  self.size = len(self.data)
41
44
  return self.size
@@ -7,6 +7,9 @@ primitive prefix to determine the size of the chunk portion.
7
7
  from typing import BinaryIO as _BinaryIO
8
8
  from typing import TypeVar
9
9
 
10
+ # third-party
11
+ from vcorelib.io import BinaryMessage
12
+
10
13
  # internal
11
14
  from runtimepy.primitives import Primitivelike, UnsignedInt, create
12
15
  from runtimepy.primitives.byte_order import (
@@ -44,7 +47,7 @@ class PrefixedChunk(Serializable):
44
47
  """Get this chunk as a string."""
45
48
  return str(self.chunk)
46
49
 
47
- def update(self, data: bytes, timestamp_ns: int = None) -> int:
50
+ def update(self, data: BinaryMessage, timestamp_ns: int = None) -> int:
48
51
  """Update this serializable from a bytes instance."""
49
52
 
50
53
  size = self.chunk.update(data, timestamp_ns=timestamp_ns)
@@ -14,6 +14,9 @@ from typing import TypeVar as _TypeVar
14
14
  from typing import Union as _Union
15
15
  from typing import cast as _cast
16
16
 
17
+ # third-party
18
+ from vcorelib.io import BinaryMessage
19
+
17
20
  # internal
18
21
  from runtimepy.primitives.byte_order import (
19
22
  DEFAULT_BYTE_ORDER as _DEFAULT_BYTE_ORDER,
@@ -134,7 +137,7 @@ class PrimitiveType(_Generic[T]):
134
137
  return _pack(byte_order.fmt + self.format, value)
135
138
 
136
139
  def decode(
137
- self, data: bytes, byte_order: _ByteOrder = _DEFAULT_BYTE_ORDER
140
+ self, data: BinaryMessage, byte_order: _ByteOrder = _DEFAULT_BYTE_ORDER
138
141
  ) -> PythonPrimitive:
139
142
  """Decode primitive based on this type."""
140
143
  return _unpack(byte_order.fmt + self.format, data)[0] # type: ignore
@@ -31,9 +31,15 @@ class IntegerBounds(NamedTuple):
31
31
  return max(self.min, min(val, self.max))
32
32
 
33
33
  @staticmethod
34
- def create(byte_count: int, signed: bool) -> "IntegerBounds":
34
+ def create_bit(bit_count: int, signed: bool) -> "IntegerBounds":
35
35
  """Compute maximum and minimum values given size and signedness."""
36
36
 
37
- min_val = 0 if not signed else -1 * (2 ** (byte_count * 8 - 1))
38
- width = 8 * byte_count if not signed else 8 * byte_count - 1
39
- return IntegerBounds(min_val, (2**width) - 1)
37
+ return IntegerBounds(
38
+ 0 if not signed else -1 * (2 ** (bit_count - 1)),
39
+ (2 ** (bit_count if not signed else bit_count - 1)) - 1,
40
+ )
41
+
42
+ @staticmethod
43
+ def create(byte_count: int, signed: bool) -> "IntegerBounds":
44
+ """Compute maximum and minimum values given size and signedness."""
45
+ return IntegerBounds.create_bit(8 * byte_count, signed)
@@ -20,6 +20,7 @@ from vcorelib.io.types import JsonValue as _JsonValue
20
20
  from runtimepy.registry.item import RegistryItem as _RegistryItem
21
21
  from runtimepy.registry.name import NameRegistry as _NameRegistry
22
22
  from runtimepy.registry.name import RegistryKey as _RegistryKey
23
+ from runtimepy.registry.name import is_registry_key as _is_registry_key
23
24
  from runtimepy.schemas import RuntimepyDictCodec as _RuntimepyDictCodec
24
25
 
25
26
  T = _TypeVar("T", bound=_RegistryItem)
@@ -66,6 +67,15 @@ class Registry(_RuntimepyDictCodec, _Generic[T]):
66
67
  for name, item in self.items.items()
67
68
  }
68
69
 
70
+ def register_from_other(self, other: "Registry[T]") -> None:
71
+ """Register missing elements from another registry."""
72
+
73
+ for name, instance in other.items.items():
74
+ if name in self.items:
75
+ assert self.items[name].id == instance.id, (name, instance)
76
+ else:
77
+ assert self.register(name, instance), (name, instance)
78
+
69
79
  def register(self, name: str, item: T) -> bool:
70
80
  """Attempt to register a new item."""
71
81
 
@@ -100,6 +110,16 @@ class Registry(_RuntimepyDictCodec, _Generic[T]):
100
110
  result = self.items[name]
101
111
  return result
102
112
 
113
+ def registry_normalize(self, key: _RegistryKey | T) -> T:
114
+ """Attempt to get an item from a registry key."""
115
+
116
+ if _is_registry_key(key):
117
+ result: T = self[_cast(str, key)]
118
+ else:
119
+ result = _cast(T, key)
120
+
121
+ return result
122
+
103
123
  def __getitem__(self, key: _RegistryKey) -> T:
104
124
  """Get a registry item."""
105
125
 
@@ -3,6 +3,7 @@ A simple name-to-identifier registry interface.
3
3
  """
4
4
 
5
5
  # built-in
6
+ from typing import Any as _Any
6
7
  from typing import MutableMapping as _MutableMapping
7
8
  from typing import Optional as _Optional
8
9
  from typing import Union as _Union
@@ -15,6 +16,11 @@ KeyToName = _MutableMapping[int, str]
15
16
  NameToKey = _MutableMapping[str, int]
16
17
 
17
18
 
19
+ def is_registry_key(data: _Any) -> bool:
20
+ """Determine if this data is a registry key."""
21
+ return isinstance(data, (int, str))
22
+
23
+
18
24
  class NameRegistry(_TwoWayNameMapping[int]):
19
25
  """A simple class for keeping track of name-to-identifier mappings."""
20
26