11from logging import getLogger
22import struct
3- from typing import Union
3+ from typing import Callable , Union
44
55from openlcb .cdivar import SUBTYPE_FORMATS , CDIVar
66from openlcb .memoryspace import MemorySpace
99
1010
1111class Segment :
12+ """A memory segment that is contiguous or behaves as such.
13+ """
1214 def __init__ (self , size = 0 , readOnly = False ):
1315 assert isinstance (size , int )
1416 assert size >= 0
@@ -78,7 +80,7 @@ def extend(self, data):
7880 self ._data += data
7981
8082 def setSlice (self , address : int , data : Union [bytearray , bytes ],
81- size : Union [int , None ] = None , force = True ):
83+ size : Union [int , None ] = None , force = True ):
8284 assert isinstance (data , (bytearray , bytes ))
8385 assert isinstance (address , int )
8486 assert address >= 0
@@ -109,16 +111,37 @@ def size(self) -> int:
109111
110112
111113class MemoryManager :
114+ """A collection of memory segments.
115+ Attributes:
116+ watchVars (Dict[int, Dict[int, CDIVar]]): Variables that will be
117+ used as the argument to callbacks, as well as set when
118+ memory is set, at the address corresponding to the 2nd
119+ tier's integer key. The 1st tier of the dict is keyed by a
120+ space int.
121+ - If not present, and setSlice is used (without the optional
122+ callbackVar) no registered callbacks will be called (Not
123+ enough information).
124+
125+ """
126+
112127 def __init__ (self ):
113128 self ._segments = {} # type: dict[int, Segment]
129+ self ._writeListeners = []
130+ self .watchVars = {} # type: dict[int, dict[int, CDIVar]]
114131
115132 def set (self , var : CDIVar ):
116- assert isinstance ( var , CDIVar )
133+ assert issubclass ( type ( var ) , CDIVar )
117134 assert var .space is not None
118- assert var .address
135+ assert var .address is not None
119136 data = var .getData ()
120137 assert data is not None
121- self .setSlice (var .space , var .address , data , size = var .size )
138+ watchVar = self .getWatchVar (var .space , var .address )
139+ if watchVar is not None :
140+ var = watchVar
141+ assert var .space is not None
142+ assert var .address is not None
143+ self .setSlice (var .space , var .address , data , size = var .size ,
144+ callbackVar = var )
122145
123146 def getFirst (self , space : Union [MemorySpace , int ]):
124147 """Get first address"""
@@ -196,7 +219,7 @@ def get(self, var: CDIVar) -> CDIVar:
196219 Returns:
197220 CDIVar: Same var instance (returned by reference) modified.
198221 """
199- assert isinstance ( var , CDIVar )
222+ assert issubclass ( type ( var ) , CDIVar )
200223 assert var .space is not None
201224 assert var .address
202225 assert var .size is not None
@@ -206,8 +229,13 @@ def get(self, var: CDIVar) -> CDIVar:
206229 return var
207230
208231 def setSlice (self , space : Union [MemorySpace , int ], address : int ,
209- data : Union [bytes , bytearray ], size = None ):
210- """Set address in virtual memory space to data"""
232+ data : Union [bytes , bytearray ], size = None ,
233+ callbackVar : Union [CDIVar , None ] = None ) -> Segment :
234+ """Set address in virtual memory space to data.
235+ fireWriteListeners can only be called if
236+ callbackVar is known, otherwise type information
237+ is not available at this level.
238+ """
211239 assert isinstance (data , (bytearray , bytes ))
212240 if isinstance (space , MemorySpace ):
213241 space = space .value
@@ -223,6 +251,18 @@ def setSlice(self, space: Union[MemorySpace, int], address: int,
223251 segment = Segment ()
224252 self ._segments [space ] = segment
225253 segment .setSlice (address , data , size = size )
254+ if callbackVar is None :
255+ callbackVar = self .getWatchVar (space , address )
256+ if callbackVar is not None :
257+ print (f"[setSlice] getWatchVar { callbackVar .className } " )
258+ else :
259+ print (f"[setSlice] getWatchVar (None)" )
260+ else :
261+ print (f"[setSlice] callbackVar { callbackVar .className } " )
262+ if callbackVar is not None :
263+ callbackVar .setData (data )
264+ self .fireWriteListeners (callbackVar )
265+ return segment
226266
227267 def getSlice (self , space : Union [MemorySpace , int ], address : int ,
228268 size : int , force = False ) -> bytearray :
@@ -255,7 +295,14 @@ def setInt(self, space: Union[MemorySpace, int], address: int,
255295 data = struct .pack (dataFormat , value )
256296 assert len (data ) == size , \
257297 f"Expected { size } byte(s) for { typeStr } , got { len (data )} "
258- return self .setSlice (space , address , data )
298+ var = self .getWatchVar (space , address )
299+ if var is None :
300+ var = CDIVar ("int" , _size = size , _no_min = True , _no_max = True ,
301+ space = space , address = address )
302+ result = self .setSlice (space , address , data , callbackVar = var )
303+ if self ._writeListeners :
304+ self .fireWriteListeners (var )
305+ return result
259306
260307 def getInt (self , space : Union [MemorySpace , int ], address : int ,
261308 size : int , signed : bool ) -> int :
@@ -289,7 +336,14 @@ def setFloat(self, space: Union[MemorySpace, int], address: int,
289336 data = struct .pack (dataFormat , value )
290337 assert len (data ) == size , \
291338 f"Expected { size } byte(s) for { typeStr } , got { len (data )} "
292- return self .setSlice (space , address , data )
339+ var = self .getWatchVar (space , address )
340+ if var is None :
341+ var = CDIVar ("float" , _size = size , _no_min = True , _no_max = True ,
342+ space = space , address = address )
343+ result = self .setSlice (space , address , data , callbackVar = var )
344+ if self ._writeListeners :
345+ self .fireWriteListeners (var )
346+ return result
293347
294348 def getFloat (self , space : Union [MemorySpace , int ], address : int ,
295349 size : int ) -> float :
@@ -306,3 +360,80 @@ def getFloat(self, space: Union[MemorySpace, int], address: int,
306360 assert len (values ) == 1 , f"Expected 1 { typeStr } , got { len (values )} "
307361 assert isinstance (values [0 ], float )
308362 return values [0 ]
363+
364+ def registerWriteListener (self , callback : Callable [[CDIVar ], None ]):
365+ """Register a function to call when a value is written.
366+ NOTE: You must also call registerWatchVar or low-level
367+ (setSlice) calls will not trigger a callback due to not enough
368+ information (callback takes a CDIVar).
369+ """
370+ self ._writeListeners .append (callback )
371+
372+ def registerWatchVar (self , var : CDIVar ):
373+ """Register a CDIVar to track writes.
374+
375+ Arguments:
376+ var (CDIVar): Must have var.space
377+ and var.address in order to be tracked. This var's value
378+ will be edited remotely, and will allow write listeners
379+ to fire even when setting memory of a non-number or
380+ unspecified type (using setSlice).
381+ Raises:
382+ AssertionError: space or address is None.
383+ """
384+ # a.k.a. setWatchVar
385+ assert var .space is not None , \
386+ 'cdivar.space is required in order to listen for change'
387+ if isinstance (var .space , MemorySpace ):
388+ var .space = var .space .value
389+ assert isinstance (var .space , int )
390+ assert var .address is not None , \
391+ 'cdivar.address is required in order to listen for change'
392+ assert isinstance (var .address , int )
393+ if var .space not in self .watchVars :
394+ self .watchVars [var .space ] = {}
395+ if var .address in self .watchVars [var .space ]:
396+ raise KeyError (
397+ f"Address { var .address } of space { var .space } "
398+ " is already registered." )
399+ self .watchVars [var .space ][var .address ] = var
400+
401+ def getWatchVar (self , space : Union [MemorySpace , int ], address : int ,
402+ default : Union [CDIVar , None ] = None ):
403+ assert space is not None
404+ assert address is not None
405+ if isinstance (space , MemorySpace ):
406+ space = space .value
407+ assert isinstance (space , int )
408+ assert isinstance (address , int )
409+ if default is not None :
410+ assert issubclass (type (default ), CDIVar )
411+ spaceVars = self .watchVars .get (space )
412+ if spaceVars is None :
413+ return None
414+ var = spaceVars .get (address , default )
415+ if var is not None :
416+ # Fix space & address so fireWriteListeners works correctly
417+ if var .space is None :
418+ logger .warning (
419+ f"Setting var.space={ space } using its location" )
420+ var .space = space
421+ elif var .space != space :
422+ logger .warning (
423+ f"Setting incorrect var.space { var .space } "
424+ f" to { space } using its location" )
425+ var .space = space
426+ if var .address is None :
427+ logger .warning (
428+ f"Setting var.address={ address } using its location" )
429+ var .address = address
430+ elif var .address != address :
431+ logger .warning (
432+ f"Setting incorrect var.address { var .address } "
433+ f" to { address } using its location" )
434+ var .address = address
435+ return var
436+
437+ def fireWriteListeners (self , var : CDIVar ):
438+ for writeListener in self ._writeListeners :
439+ writeListener (var )
0 commit comments