; ***************************************************************************
; copyright   : (C) 2026 by Martin Preuss
; email       : martin@libchipcard.de
;
; ***************************************************************************
; * This file is part of the project "AqHome".                              *
; * Please see toplevel file COPYING of that project for license details.   *
; ***************************************************************************

#ifndef AQH_AVR_GUI2_OBJECT_ASM
#define AQH_AVR_GUI2_OBJECT_ASM


; ***************************************************************************
; @module OBJECT
;
; Central to this module is the signal handler (@ref OBJ_HandleSignal).
;
; Inputs:
; - Y:       Pointer to the object to handle a signal
; - R16:     Signal number (e.g. @ref OBJECT_SIGNAL_DESTROY)
; - R17:     Selector
;            Normally this comes from the object var OBJECT_OFFS_SELECTOR but it can
;            have different meanings for different signals.
;            For signals WIDGET_SIGNAL_SETVALUE and WIDGET_SIGNAL_GETVALUE from the
;            @ref WIDGET module this contains the id of the value to reference, e.g.
;            to set the X position of a widget R17 has the value WIDGET_VALUE_X.
; - X:       Parameter for the signal (either as 16-bit value or as two 8-bit values
;            in xl and xh). E.g. for signal WIDGET_SIGNAL_SETVALUE this would contain
;            the value to assign
;
; Outputs:
; - CFLAG:   Set if signal handled.
; - R19:R18: Response from the signal handler. Most signals do not have return values, but
;            especially the signal WIDGET_SIGNAL_GETVALUE is expected to return a result.
;



; ***************************************************************************
; defines

; Object in flash
.equ OBJECT_OFFS_NEXT_LO              =  0
.equ OBJECT_OFFS_NEXT_HI              =  1
.equ OBJECT_OFFS_PARENT_LO            =  2
.equ OBJECT_OFFS_PARENT_HI            =  3
.equ OBJECT_OFFS_CHILD_LO             =  4
.equ OBJECT_OFFS_CHILD_HI             =  5
.equ OBJECT_OFFS_FLAGS                =  6
.equ OBJECT_OFFS_SELECTOR             =  7
.equ OBJECT_OFFS_TARGET_LO            =  8
.equ OBJECT_OFFS_TARGET_HI            =  9
.equ OBJECT_OFFS_SIGNALMAP_LO         = 10  ; byte address (for LPM!)
.equ OBJECT_OFFS_SIGNALMAP_HI         = 11
.equ OBJECT_OFFS_OPTS                 = 12
.equ OBJECT_SIZE                      = 13

.equ OBJECT_FLAGS_DESTROY_BIT         =  0 ; scheduled for destruction

; object opts_lo
.equ OBJECT_OPTS_TIMER_BIT          =  7
.equ OBJECT_OPTS_MSGRECV_BIT        =  6

; SignalMap entries
.equ OBJECT_SIGNALMAP_OFFS_HEADER     = 0
.equ OBJECT_SIGNALMAP_OFFS_ENTRIES    = 2

.equ OBJECT_SIGNALMAPENTRY_OFFS_SELECTOR   = 0
.equ OBJECT_SIGNALMAPENTRY_OFFS_SIGNAL     = 1 ; end if 0
.equ OBJECT_SIGNALMAPENTRY_OFFS_HANDLER_LO = 2
.equ OBJECT_SIGNALMAPENTRY_OFFS_HANDLER_HI = 3

; signals
.equ OBJECT_SIGNAL_NONE               =  0
.equ OBJECT_SIGNAL_DESTROY            =  1
.equ OBJECT_SIGNAL_TIMER              =  2
.equ OBJECT_SIGNAL_RECVMSG            =  3  ; X=msg
.equ OBJECT_SIGNAL_NEXTFREE           =  4



; ***************************************************************************
; code

.cseg



; ---------------------------------------------------------------------------
; @routine Object_Alloc @global
;
; Allocate memory and preset it with zeroes.
;
; @return CFLAG set if object created, cleared otherwise
; @return Y address of created object in SDRAM (if CFLAG set)
; @param r25:r24 size of object to allocate
; @clobbers !r16, !r17, !X

Object_Alloc:
  push xl
    push xh
      push r16
        push r17
          bigcall Heap_AllocAndZero
          mov yl, xl
          mov yh, xh
        pop r17
      pop r16
    pop xh
  pop xl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_Init @global
;
; @param Y address of object in SDRAM
; @param r16 value for OBJECT_OFFS_OPTS
; @clobbers r16, r17, X

OBJ_Init:
  ; set opts
  std Y+OBJECT_OFFS_OPTS, r16
  
  ; set default signal map
  ldi r16, LOW(Object_DefaultSignalmap*2)
  std Y+OBJECT_OFFS_SIGNALMAP_LO, r16
  ldi r16, HIGH(Object_DefaultSignalmap*2)
  std Y+OBJECT_OFFS_SIGNALMAP_HI, r16
  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_Fini @global
;
; @param Y address of object in SDRAM
; @clobbers none

OBJ_Fini:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_Free @global
;
; Sends a OBJECT_SIGNAL_DESTROY signal to all children and to the given
; object which frees all objects from the Heap.
; CAVEAT: Y is invalid after return!
;
; @param Y address of object in SDRAM
; @clobbers any, !Y

OBJ_Free:
  tst yl
  brne OBJ_Free_notNull
  tst yh
  brne OBJ_Free_notNull
  rjmp OBJ_Free_ret
  
OBJ_Free_notNull:
  ldi r16, OBJECT_SIGNAL_DESTROY
  clr r17
  rcall OBJ_TreeHandleSignalChildenFirst ; (any, !R16, !R17, !X, !Y)

OBJ_Free_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_OnDestroy @global
;
; Releases given object from heap.
; CAVEAT: Y is invalid after return!
;
; @param Y address of object in SDRAM
; @return CFLAG set if signal handled
; @clobbers r16, r17, r18, r19, r20, r21, r24, r25, X

OBJ_OnDestroy:
  rcall OBJ_Unlink                ; (r16, r17, r18, r19, r20, r21, X)
  mov xl, yl
  mov xh, yh
  bigcall Heap_Free               ; (r16, r17, r24, r25, X)
  sec
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetNext @global
;
; @param Y address of object
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object
; @clobbers none

OBJ_GetNext:
  tst yl
  brne OBJ_GetNext_notNull
  tst yh
  brne OBJ_GetNext_notNull
  rjmp OBJ_GetNext_clcRet
OBJ_GetNext_notNull:
  ldd r18, Y+OBJECT_OFFS_NEXT_LO
  ldd r19, Y+OBJECT_OFFS_NEXT_HI
  tst r18
  brne OBJ_GetNext_secRet
  tst r19
  brne OBJ_GetNext_secRet
OBJ_GetNext_clcRet:
  clc
  rjmp OBJ_GetNext_ret
OBJ_GetNext_secRet:
  sec
OBJ_GetNext_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetParent @global
;
; @param Y address of object
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object
; @clobbers none

OBJ_GetParent:
  tst yl
  brne OBJ_GetParent_notNull
  tst yh
  brne OBJ_GetParent_notNull
  rjmp OBJ_GetParent_clcRet
OBJ_GetParent_notNull:
  ldd r18, Y+OBJECT_OFFS_PARENT_LO
  ldd r19, Y+OBJECT_OFFS_PARENT_HI
  tst r18
  brne OBJ_GetParent_secRet
  tst r19
  brne OBJ_GetParent_secRet
OBJ_GetParent_clcRet:
  clc
  rjmp OBJ_GetParent_ret
OBJ_GetParent_secRet:
  sec
OBJ_GetParent_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetFirstChild @global
;
; @param Y address of object
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object
; @clobbers none

OBJ_GetFirstChild:
  tst yl
  brne OBJ_GetFirstChild_notNull
  tst yh
  brne OBJ_GetFirstChild_notNull
  rjmp OBJ_GetFirstChild_clcRet
OBJ_GetFirstChild_notNull:
  ldd r18, Y+OBJECT_OFFS_CHILD_LO
  ldd r19, Y+OBJECT_OFFS_CHILD_HI
  tst r18
  brne OBJ_GetFirstChild_secRet
  tst r19
  brne OBJ_GetFirstChild_secRet
OBJ_GetFirstChild_clcRet:
  clc
  rjmp OBJ_GetFirstChild_ret
OBJ_GetFirstChild_secRet:
  sec
OBJ_GetFirstChild_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetLastChild @global
;
; @param Y address of object
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object
; @clobbers none

OBJ_GetLastChild:
  push yl
    push yh
      rcall OBJ_GetFirstChild     ; R19:R18=obj (none)
      brcc OBJ_GetLastChild_ret

OBJ_GetLastChild_loop:
        mov yl, r18
        mov yh, r19
        rcall OBJ_GetNext         ; R19:R18=obj (none)
      brcs OBJ_GetLastChild_loop
      mov r18, yl
      mov r19, yh
      sec
OBJ_GetLastChild_ret:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetChildAt @global
;
; @param Y   address of object
; @param R16 idx of child to get (starting at 0)
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object
; @clobbers r16

OBJ_GetChildAt:
  push yl
    push yh
      rcall OBJ_GetFirstChild     ; R19:R18=obj (none)
      brcc OBJ_GetChildAt_ret
OBJ_GetChildAt_loop:
        tst r16
        breq OBJ_GetChildAt_secRet
        mov yl, r18
        mov yh, r19
        dec r16
        rcall OBJ_GetNext         ; R19:R18=obj (none)
      brcs OBJ_GetChildAt_loop
      rjmp OBJ_GetChildAt_ret     ; idx too high, not found
OBJ_GetChildAt_secRet:
      sec
OBJ_GetChildAt_ret:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetChildBySelector @global
;
; @param Y   address of object
; @param R16 selector to look for
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object
; @clobbers r17

OBJ_GetChildBySelector:
  push yl
    push yh
      rcall OBJ_GetFirstChild     ; R19:R18=obj (none)
OBJ_GetChildBySelector_loop:
        brcc OBJ_GetChildBySelector_ret
        mov yl, r18
        mov yh, r19
        ldd r17, Y+OBJECT_OFFS_SELECTOR
        cp r17, r16
        breq OBJ_GetChildBySelector_secRet
        rcall OBJ_GetNext         ; R19:R18=obj (none)
      rjmp OBJ_GetChildBySelector_loop
OBJ_GetChildBySelector_secRet:
      sec
OBJ_GetChildBySelector_ret:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_TreeDestroyMarked @global
;
; @param Y   address of root object
; @clobbers any

OBJ_TreeDestroyMarked:
  ldd r16, Y+OBJECT_OFFS_FLAGS
  sbrc r16, OBJECT_FLAGS_DESTROY_BIT
    rjmp OBJ_TreeDestroyMarked_destroy

  rcall OBJ_GetFirstChild     ; R19:R18=obj (none)
  brcc OBJ_TreeDestroyMarked_ret
OBJ_TreeDestroyMarked_loop:
    mov yl, r18
    mov yh, r19
    rcall OBJ_GetNext         ; R19:R18=obj (none)
    brcs OBJ_TreeDestroyMarked_handleChild
    clr r18
    clr r19
OBJ_TreeDestroyMarked_handleChild:
    push r18
      push r19
        rcall OBJ_TreeDestroyMarked  ; recursion
      pop r19
    pop r18
    mov r16, r18
    or r16, r19
    breq OBJ_TreeDestroyMarked_ret
  rjmp OBJ_TreeDestroyMarked_loop
OBJ_TreeDestroyMarked_destroy:
  rcall OBJ_Free
OBJ_TreeDestroyMarked_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_SkipObjects @global
;
; @param Y address of object
; @param R16 number of objects to skip
; @return CFLAG set, if found, cleared otherwise
; @return r19:r18 resulting object
; @clobbers r16

OBJ_SkipObjects:
  push yl
    push yh
      tst r16
      clc
      breq OBJ_SkipObjects_ret
OBJ_SkipObjects_loop:
        rcall OBJ_GetNext
        brcc OBJ_SkipObjects_ret
        dec r16
        breq OBJ_SkipObjects_done
        mov yl, r18
        mov yh, r19
      rjmp OBJ_SkipObjects_loop
OBJ_SkipObjects_done:
      sec
OBJ_SkipObjects_ret:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_AddChild @global
;
; @param Y object to add
; @param X object to add to
; @clobbers r18, r19

OBJ_AddChild:
  mov r18, yl   ; switch X/Y
  mov yl, xl
  mov xl, r18
  mov r18, yh
  mov yh, xh
  mov xh, r18
    rcall objAddObjXAsChildToY
  mov r18, yl   ; switch X/Y
  mov yl, xl
  mov xl, r18
  mov r18, yh
  mov yh, xh
  mov xh, r18
  ret
; @end


; ---------------------------------------------------------------------------
; @routine objAddObjXAsChildToY
;
; @param Y designated parent
; @param X object to add
; @clobbers r18, r19

objAddObjXAsChildToY:
  rcall OBJ_GetLastChild                 ; r19:r18=last child (r18, r19)
  brcc objAddObjXAsChildToY_setAsFirstChild
  ; append to last child
  push yl
    push yh
      mov yl, r18
      mov yh, r19
      std Y+OBJECT_OFFS_NEXT_LO, xl
      std Y+OBJECT_OFFS_NEXT_HI, xh
    pop yh
  pop yl
  rjmp objAddObjXAsChildToY_setParent
objAddObjXAsChildToY_setAsFirstChild:
  std Y+OBJECT_OFFS_CHILD_LO, xl
  std Y+OBJECT_OFFS_CHILD_HI, xh
objAddObjXAsChildToY_setParent:
  adiw xh:xl, OBJECT_OFFS_PARENT_LO
  st X+, yl
  st X, yh
  sbiw xh:xl, (OBJECT_OFFS_PARENT_LO+1)
  ret
; @end




; ---------------------------------------------------------------------------
; @routine OBJ_Unlink @global
;
; @param Y object to unlink
; @clobbers r16, r17, r18, r19, r20, r21

OBJ_Unlink:
  ldd r20, Y+OBJECT_OFFS_NEXT_LO             ; r21:r20=next
  ldd r21, Y+OBJECT_OFFS_NEXT_HI

  push yl
    push yh
      rcall objGetPredecessor                ; Y=predecessor (r16, r17, Y)
      brcc OBJ_Unlink_l1
      ; unlink from predecessor
      std Y+OBJECT_OFFS_NEXT_LO, r20         ; copy NEXT to predecessor
      std Y+OBJECT_OFFS_NEXT_HI, r21
      rjmp OBJ_Unlink_l2
OBJ_Unlink_l1:                               ; no predecessor, unlink from parent
    pop yh
  pop yl
  rcall OBJ_GetParent
  brcc OBJ_Unlink_clearTreePtrs
  push yl
    push yh
      mov yl, r18
      mov yh, r19
      std Y+OBJECT_OFFS_CHILD_LO, r20
      std Y+OBJECT_OFFS_CHILD_HI, r21
OBJ_Unlink_l2:
    pop yh
  pop yl
OBJ_Unlink_clearTreePtrs:
  clr r16
  std Y+OBJECT_OFFS_NEXT_LO, r16
  std Y+OBJECT_OFFS_NEXT_HI, r16
  std Y+OBJECT_OFFS_PARENT_LO, r16
  std Y+OBJECT_OFFS_PARENT_HI, r16
OBJ_Unlink_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_GetRootToY @global
;
; @param  Y  address of object
; @return Y  address pointer to root object
; @clobbers r18, r19

OBJ_GetRootToY:
OBJ_GetRootToY_loop:
    rcall OBJ_GetParent          ; (none)
    brcc OBJ_GetRootToY_found    ; no parent, return current Y
    mov yl, r18
    mov yh, r19
  rjmp OBJ_GetRootToY_loop
OBJ_GetRootToY_found:
  sec
OBJ_GetRootToY_end:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_EmitSignal @global
;
; @param Z byte address of object (for LPM!)
; @param R16 signal number
; @param xl param1
; @param xh param2
; @clobbers any, !Y

OBJ_EmitSignal:
  ldd r17, Y+OBJECT_OFFS_SELECTOR
  rjmp OBJ_EmitSignalWithSelector
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_EmitSignalWithSelector @global
;
; @param Z byte address of object (for LPM!)
; @param R16 signal number
; @param R17 selector
; @param xl param1
; @param xh param2
; @clobbers any, !Y

OBJ_EmitSignalWithSelector:
  push yl
    push yh
      ldd r23, Y+OBJECT_OFFS_TARGET_LO
      ldd yh, Y+OBJECT_OFFS_TARGET_HI
      mov yl, r23
      rcall OBJ_HandleSignal            ; (any, !Y)
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_HandleSignal @global
;
; @param Y address of object
; @param R16 signal number
; @param R17 selector
; @param xl param1
; @param xh param2
; @return CFLAG set if handled, cleared otherwise
; @return r19:r18 return value from signal handler
; @clobbers any, !Y

OBJ_HandleSignal:
  ; check pointer
  mov r18, yl
  or r18, yh
  clc
  breq OBJ_HandleSignal_ret
  
  ; start with objects signal map
  ldd zl, Y+OBJECT_OFFS_SIGNALMAP_LO
  ldd zh, Y+OBJECT_OFFS_SIGNALMAP_HI

OBJ_HandleSignal_loop:
    ; test table validity
    mov r18, zl
    or r18, zh
    clc
    breq OBJ_HandleSignal_ret
    ; try this table
    push zl
      push zh
        push r16
          push r17
            push xl
              push xh
                rcall objHandleSignalWithMap  ; (any, !Y)
              pop xh
            pop xl
          pop r17
        pop r16
      pop zh
    pop zl
    brcs OBJ_HandleSignal_ret
    ; get next table
    lpm r18, Z+
    lpm zh, Z
    mov zl, r18
  rjmp OBJ_HandleSignal_loop    ; try next table
OBJ_HandleSignal_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_TreeHandleSignal @global @recursive
;
; @param  Y address of object
; @param  R16 signal number
; @param  R17 selector
; @param  xl param1
; @param  xh param2
; @return CFLAG set if handled, cleared otherwise
; @clobbers any, !R16, !R17, !X, !Y

OBJ_TreeHandleSignal:
  push yl
    push yh
      ; call signal handler
      push r16
        push r17
          push xl
            push xh
              rcall OBJ_HandleSignal      ; (any, !Y)
            pop xh
          pop xl
        pop r17
      pop r16
      ; handle children
      rcall OBJ_GetFirstChild             ; r19:r18=object (none)
      brcc OBJ_TreeHandleSignal_done
OBJ_TreeHandleSignal_loop:
        mov yl, r18
        mov yh, r19
        rcall OBJ_TreeHandleSignal        ; recursion!
        rcall OBJ_GetNext                 ; r19:r18=object (none)
      brcs OBJ_TreeHandleSignal_loop
OBJ_TreeHandleSignal_done:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_TreeHandleSignalIfMatchingOpts @global @recursive
;
; @param  Y address of object
; @param  R16 signal number
; @param  R17 selector
; @param  xl param1
; @param  xh param2
; @param  r20 mask for OBJECT_OFFS_OPTS
; @param  r21 value for OBJECT_OFFS_OPTS to match
; @clobbers any, !R16, !R17, !X, !Y

OBJ_TreeHandleSignalIfMatchingOpts:
  push yl
    push yh
      ; call signal handler
      push r16
        push r17
          push xl
            push xh
              ldd r18, Y+OBJECT_OFFS_OPTS
              eor r18, r21
              and r18, r20
              brne OBJ_TreeHandleSignalIfMatchingOpts_l1
              push r20
                push r21
                  rcall OBJ_HandleSignal   ; (any, !Y)
                pop r21
              pop r20
OBJ_TreeHandleSignalIfMatchingOpts_l1:
            pop xh
          pop xl
        pop r17
      pop r16
      ; handle children
      rcall OBJ_GetFirstChild             ; r19:r18=object (none)
OBJ_TreeHandleSignalIfMatchingOpts_loop:
        brcc OBJ_TreeHandleSignalIfMatchingOpts_done
        mov yl, r18
        mov yh, r19
        rcall OBJ_TreeHandleSignalIfMatchingOpts        ; recursion!
        rcall OBJ_GetNext                 ; r19:r18=object (none)
      rjmp OBJ_TreeHandleSignalIfMatchingOpts_loop
OBJ_TreeHandleSignalIfMatchingOpts_done:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_TreeHandleSignalIfFlagsSet @global @recursive
;
; @param  Y address of object
; @param  R16 signal number
; @param  R17 selector
; @param  xl param1
; @param  xh param2
; @param  r20 flags to match
; @clobbers any, !R16, !R17, !X, !Y

OBJ_TreeHandleSignalIfFlagsSet:
  push yl
    push yh
      ; call signal handler
      push r16
        push r17
          push xl
            push xh
              ldd r18, Y+OBJECT_OFFS_FLAGS
              eor r18, r20
              and r18, r20
              brne OBJ_TreeHandleSignalIfFlagsSet_l1
              push r20
                rcall OBJ_HandleSignal                ; (any, !Y)
              pop r20
OBJ_TreeHandleSignalIfFlagsSet_l1:
            pop xh
          pop xl
        pop r17
      pop r16
      ; handle children
      rcall OBJ_GetFirstChild                         ; r19:r18=object (none)
OBJ_TreeHandleSignalIfFlagsSet_loop:
        brcc OBJ_TreeHandleSignalIfFlagsSet_done
        mov yl, r18
        mov yh, r19
        rcall OBJ_TreeHandleSignalIfFlagsSet          ; recursion!
        rcall OBJ_GetNext                             ; r19:r18=object (none)
      rjmp OBJ_TreeHandleSignalIfFlagsSet_loop
OBJ_TreeHandleSignalIfFlagsSet_done:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_TreeHandleSignalChildenFirst @global @recursive
;
; Sends a given signal to a tree of objects, handling children first.
; After sending a signal to a child object that object is no longer accessed
; from within this routine, so it can be used to send a DESTROY signal which
; frees the object from the memory heap.
;
; @param  Y address of object
; @param  R16 signal number
; @param  R17 selector
; @param  xl param1
; @param  xh param2
; @clobbers any, !R16, !R17, !X, !Y

OBJ_TreeHandleSignalChildenFirst:
  push yl
    push yh
      ; handle children first
      rcall OBJ_GetFirstChild                        ; r19:r18=object (none)
      brcc OBJ_TreeHandleSignalChildenFirst_done
OBJ_TreeHandleSignalChildenFirst_loop:
        mov yl, r18
        mov yh, r19
        rcall OBJ_GetNext                            ; r19:r18=object (none)
        brcs OBJ_TreeHandleSignalChildenFirst_handle
        clr r18
        clr r19
OBJ_TreeHandleSignalChildenFirst_handle:
        push r18
          push r19
            rcall OBJ_TreeHandleSignalChildenFirst ; recursion!
          pop r19
        pop r18
        mov r20, r18
        or r20, r19
      brne OBJ_TreeHandleSignalChildenFirst_loop
OBJ_TreeHandleSignalChildenFirst_done:
    pop yh
  pop yl
  ; call signal handler on self
  push r16
    push r17
      push xl
        push xh
          rcall OBJ_HandleSignal   ; (any, !Y)
        pop xh
      pop xl
    pop r17
  pop r16
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_AddFlagsDown @global
;
; @param  Y address of object
; @param  R16 flags to add
; @clobbers r17, r18, r19

OBJ_AddFlagsDown:
  push yl
    push yh
      ldd r17, Y+OBJECT_OFFS_FLAGS
      or r17, r16
      std Y+OBJECT_OFFS_FLAGS, r17
      ; handle children
      rcall OBJ_GetFirstChild             ; r19:r18=object (none)
OBJ_AddFlagsDown_loop:
        brcc OBJ_AddFlagsDown_done
        mov yl, r18
        mov yh, r19
        rcall OBJ_AddFlagsDown            ; recursion!
        rcall OBJ_GetNext                 ; r19:r18=object (none)
      rjmp OBJ_AddFlagsDown_loop
OBJ_AddFlagsDown_done:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_SubFlagsDown @global
;
; @param  Y address of object
; @param  R16 flags to subtract
; @clobbers r17, r18, r19

OBJ_SubFlagsDown:
  push yl
    push yh
      ldd r17, Y+OBJECT_OFFS_FLAGS
      com r16
      and r17, r16
      com r16
      std Y+OBJECT_OFFS_FLAGS, r17
      ; handle children
      rcall OBJ_GetFirstChild             ; r19:r18=object (none)
OBJ_SubFlagsDown_loop:
        brcc OBJ_SubFlagsDown_done
        mov yl, r18
        mov yh, r19
        rcall OBJ_SubFlagsDown            ; recursion!
        rcall OBJ_GetNext                 ; r19:r18=object (none)
      rjmp OBJ_SubFlagsDown_loop
OBJ_SubFlagsDown_done:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_AddFlagsUp @global @recursive
;
; @param  Y address of object
; @param  R16 flags to add
; @clobbers r17, r18, r19

OBJ_AddFlagsUp:
  push yl
    push yh
      ldd r17, Y+OBJECT_OFFS_FLAGS
      or r17, r16
      std Y+OBJECT_OFFS_FLAGS, r17
      ; handle parents
OBJ_AddFlagsUp_loop:
        rcall OBJ_GetParent             ; r19:r18=object (none)
        brcc OBJ_AddFlagsUp_done
        mov yl, r18
        mov yh, r19
        rcall OBJ_AddFlagsUp            ; recursion!
      rjmp OBJ_AddFlagsUp_loop
OBJ_AddFlagsUp_done:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine objHandleSignalWithMap
;
; @param Y byte address of object (for LPM!)
; @param Z signal map to use
; @param R16 signal number
; @param R17 selector
; @param xl param1
; @param xh param2
; @return CFLAG set if handled, cleared otherwise
; @return r19:r18 return value from signal handler
; @clobbers any, !Y

objHandleSignalWithMap:
  push yl
    push yh
      rcall objGetHandlerFromSignalMap      ; r19:r18=handler (r22, r23, Z)
      brcc objHandleSignalWithMap_done
      rcall objHandleSignalWithMap_jmpR19R18
objHandleSignalWithMap_done:
    pop yh
  pop yl
  ret
objHandleSignalWithMap_jmpR19R18:
  ; jmp to r19:r18 via stack
  push r18
  push r19
  ret
; @end



; ---------------------------------------------------------------------------
; @routine objGetHandlerFromSignalMap
;
; @param Y address of object
; @param Z signal map to use (byte address for LPM!)
; @param R16 signal number
; @param R17 selector
; @param xl param1
; @param xh param2
; @return CFLAG set if found, cleared otherwise
; @return R19:R18 handler found
; @clobbers r22, r23, Z

objGetHandlerFromSignalMap:
  mov r23, zl
  or r23, zh
  clc
  breq objGetHandlerFromSignalMap_done
  adiw zh:zl, OBJECT_SIGNALMAP_OFFS_ENTRIES      ; go straight to entries
objGetHandlerFromSignalMap_loop:
    lpm r22, Z+                                  ; selector
    lpm r23, Z+                                  ; signal (0=end of table)
    lpm r18, Z+                                  ; handler LOW
    lpm r19, Z+                                  ; handler HIGH
    tst r23                                      ; end of table?
    clc                        
    breq objGetHandlerFromSignalMap_done         ; yes, jmp
    cp r16, r23                                  ; signal match?
    brne objGetHandlerFromSignalMap_next         ; no, next
    tst r22
    breq objGetHandlerFromSignalMap_checkHandler ; accept any selector if it is 0 in table
    cp r17, r22                                  ; selector match?
    brne objGetHandlerFromSignalMap_next         ; no, next
objGetHandlerFromSignalMap_checkHandler:
    mov r23, r18                                 ; handler==NULL?
    or r23, r19
    clc
    breq objGetHandlerFromSignalMap_done         ; yes, done
    sec                                          ; found handler, return in r19:r18
    rjmp objGetHandlerFromSignalMap_done
objGetHandlerFromSignalMap_next:
  rjmp objGetHandlerFromSignalMap_loop
  
objGetHandlerFromSignalMap_done:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine objGetPredecessor
;
; @param Y address of object whose predecessor is to be found
; @return CFLAG set if predecessor found, cleared otherwise
; @return Y pointer to predecessor (if CFLAG set)
; @clobbers r16, r17, Y

objGetPredecessor:
  mov r16, yl                   ; R17:R16=object whose predecessor we look for
  mov r17, yh
  rcall OBJ_GetParent           ; R19:R18=obj (none)
  brcc objGetPredecessor_ret
  mov yl, r18
  mov yh, r19
  rcall OBJ_GetFirstChild       ; R19:R18=obj (none)
  brcc objGetPredecessor_ret
  mov yl, r18
  mov yh, r19
objGetPredecessor_loop:
    rcall OBJ_GetNext           ; R19:R18=obj (none)
    brcc objGetPredecessor_ret
    cp r18, r16
    brne objGetPredecessor_next
    cp r19, r17
    brne objGetPredecessor_next
    sec
    rjmp objGetPredecessor_ret
objGetPredecessor_next:
    mov yl, r18
    mov yh, r19
  rjmp objGetPredecessor_loop
objGetPredecessor_ret:
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_CountDirectChildren @global
;
; @param Y address of object whose children are to be counted
; @return r16 number of direct children
; @clobbers r18, r19

OBJ_CountDirectChildren:
  push yl
    push yh
      clr r16
      rcall OBJ_GetFirstChild
OBJ_CountDirectChildren_loop:
        brcc OBJ_CountDirectChildren_done
        inc r16
        mov yl, r18
        mov yh, r19
        rcall OBJ_GetNext
      rjmp OBJ_CountDirectChildren_loop
OBJ_CountDirectChildren_done:
    pop yh
  pop yl
  ret
; @end



; ---------------------------------------------------------------------------
; @routine OBJ_FreeChildren @global
;
; @param Y address of object whose children are to be free'd
; @clobbers any, !Y

OBJ_FreeChildren:
  push yl
    push yh
      clr r16
      rcall OBJ_GetFirstChild
      brcc OBJ_FreeChildren_done
OBJ_FreeChildren_loop:
        mov yl, r18
        mov yh, r19
        rcall OBJ_GetNext
        brcs OBJ_FreeChildren_loopFree
        clr r18
        clr r19
OBJ_FreeChildren_loopFree:
        push r18
          push r19
            rcall OBJ_Free
          pop r19
        pop r18
        mov r16, r18
        or r16, r19
      brne OBJ_FreeChildren_loop
OBJ_FreeChildren_done:
    pop yh
  pop yl
  ret
; @end



; ***************************************************************************
; data in FLASH

Object_DefaultSignalmap:
  ; header
  .dw 0  ; next table to use (none here)
  ; entries
  .db 0, OBJECT_SIGNAL_DESTROY, LOW(OBJ_OnDestroy), HIGH(OBJ_OnDestroy)
  .db 0, 0, 0, 0  ; end of table




; @endmodule


#endif

