; MyMouse
; Bit to handle screen-activewindow associative table
; Andrew Forrest
;****************************************************************************
; The program manages the table as a linked list of blocks. Each block
; contains up to a maximum number of (screen, activewindow) pairs. The head
; of the list is the most recently-accessed pair.
; All these routines should be called with Intuitionbase locked.
; No arbitration is done for access to screen table, so only one task at a
; time please.
;***************************************************************************
; USES:
;	Globals.ScreenTable	(the abstract object itself)
; EXPORTS:
;	InitialiseScreenTable()	; Call this first
;	ShutDownScreenTable()	; Call this last
;	AssociateWindow(Screen, Window)	; Insert a tuple in the table
;	IndexScreenTable(Screen) : Window	; Look up a tuple
;	PruneScreenTable()	; Remove redundant entries
;	
;***************************************************************************
; Table is represented as a linked list of blocks, each with up to
; TuplesPerChunk tuples. (Each chunk in the table is an Exec minnode.)

; The last chunk may be partially-empty, in which case the remaining spaces 
; are filled with NULL screen pointers. It is vitally important (to 
; PruneScreenTable()) that _only_ the last node is padded with NULLs. 
; ScreenTable.LastTupleOffset is the offset (from its chunk) of the last 
; non-NULL tuple in the table. This can be 0 is last node is empty (entirely 
; NULL) or undefined if there are no chunks. FirstTupleOffset is unused.

TuplesPerChunk	equ	7	; Ideally a power of 2 minus 1

	STRUCTURE	ScreenWindowTuple,0
	  APTR	  Tuple_Screen
	  APTR	  Tuple_Window
	LABEL	Tuple_SIZEOF

	STRUCTURE	ScreenTableChunk,MLN_SIZE
	  STRUCT	  Chunk_Data,Tuple_SIZEOF*TuplesPerChunk
	LABEL	Chunk_SIZEOF

	STRUCTURE	ScreenTableType,MLH_SIZE
	  WORD	  ST_FirstTupleOffset	; \ Byte offset from chunk to
	  WORD	  ST_LastTupleOffset	; / start of first/last tuple
	LABEL	ST_SIZEOF
	
; PRIVATE routine to find the tuple associated with a particular screen if it
; can be found. Takes a0=^screen; Uses a5=^globals (for ScreenTable)
; Returns (d0=^chunk; d1=tup offset | d0=NULL; d1=?); Trashes nil.
FindScreenTuple:
	pushm.l	a1/d2
	move.l	a0,d0	;\ Return a NULL if
	beq.s	.null	;/ ^screen is NULL
	move.l	ScreenTable+MLH_HEAD(a5),a1
.loop	  move.l	  MLN_SUCC(a1),d0	; d0 <- node after this one
	beq.s	.null	; fail if this is not really a node (the `next node' of the list header TAILPRED is NULL)
	  moveq	  #Chunk_Data,d1	; d1 <- offset of first tuple
	  moveq	  #TuplesPerChunk-1,d2
.dataloop	    cmp.l	    Tuple_Screen(a1,d1),a0
	beq.s	.succeed	; Exit early if we _do_ find the screen
	    addq.l	    #Tuple_SIZEOF,d1	; Get to next bit of data
	  dbra	  d2,.dataloop
	  move.l	  d0,a1	; Get the next node
	bra.s	.loop
.succeed	move.l	a1,d0	; d0 <- ^this chunk
.null	popm.l	a1/d2	; If we jump to here, d0 will equal NULL
	rts

; Routine to make screen table ready for use. Allocates all the necessary
; resources. Structure must initially be zeroed.
; Uses a5=^Globals (for ScreenTable); Trashes & Returns nil;
InitialiseScreenTable:
	; << Shares code with ShutDownScreenTable >>

; Routine to deallocate all of the screen table's resources and generally
; shut up shop. May be called even if structure is uninitialised (zeroed).
; Leaves screen table in an initialised state. (Deallocation is done 
; without keeping the list consistant betweentimes.)
; Uses a5=^Globals (for ScreenTable)
ShutDownScreenTable:
	pushm.l	a0-a2/d0-d2/a6
	move.l	Execbase,a6
	lea	ScreenTable+MLH_HEAD(a5),a2
	move.l	(a2),d2
	beq.s	.end	; Quit early if not initialised
.loop	  move.l	  d2,a1	; thisnode <- nextnode
	  move.l	  MLN_SUCC(a1),d2 ; nextnode <- thisnode.NEXT
	beq.s	.end	; Exit if no next node (in list header)
	  move.l	  #Chunk_SIZEOF,d0
	  just	  FreeMem	; Free the memory of this node
	bra.s	.loop	; Keep looping 'til end of list
.end	NEWLIST	a2	; Make listheader self-consistant again
	clr.l	ScreenTable+ST_FirstTupleOffset(a5)	; Clears TWO words
	popm.l	a0-a2/d0-d2/a6
	rts

; Routine to associate a particular window with a particular screen.
; Takes a0=^screen; a1=^window; Returns nil; Trashes nil.
; Uses a5=^globals (for ScreenTable and (locked) Intbase)
; Uses a2=^tuple; d1=last tuple offset; also a2,a3 = pointers to chunks 
AssociateWindow:
	cmp.w	#0,a0	; \ First check that we have not been
	  beq.s	  .return	; / passed a NULL screen pointer
	pushm.l	a2-a3/d0-d1
	bsr	FindScreenTuple
	move.l	d0,a2	; a2 <- pointer to chunk
	tst.l	d0
	bne.s	.found	; If FindScreenTuple(..) == NULL
	  move.l	  ScreenTable+MLH_TAILPRED(a5),a2	; Get last chunk
	  tst.l	  MLN_PRED(a2)	; Does it have a predecessor?
	  beq.s	  .new_chunk	; (If not, it isn't a proper node)
	    move.w	    ScreenTable+ST_LastTupleOffset(a5),d1
	    addq.w	    #Tuple_SIZEOF,d1	; Compute new LastTuple offset
	    cmp.w	    #Chunk_SIZEOF,d1
	  blo.s	  .enoughroom	; If last chunk is full up...
.new_chunk	    pushm.l	    a0-a1/a6
	    move.l	    Execbase,a6
	    move.l	    #Chunk_SIZEOF,d0
	    move.l	    #MEMF_CLEAR,d1
	    just	    AllocMem	; ...allocate a new one
	    popm.l	    a0-a1/a6
	    tst.l	    d0
	     beq.s	     .end	; (Can't allocate memory, so give up)
	    move.l	    d0,MLN_SUCC(a2)
	    exg	    d0,a2	; d0 <- ^prev chunk; a2 <- ^new chunk
	    move.l	    d0,MLN_PRED(a2)	
	    lea	    ScreenTable+MLH_TAIL(a5),a3	; a3 <- ^next chnk
	    move.l	    a2,MLN_PRED(a3)
	    move.l	    a3,MLN_SUCC(a2)
	    moveq	    #Chunk_Data,d1
.enoughroom	  move.w	  d1,ScreenTable+ST_LastTupleOffset(a5)
	  move.l	  a0,Tuple_Screen(a2,d1.w)
.found	move.l	a1,Tuple_Window(a2,d1.w)
.end	popm.l	a2-a3/d0-d1
.return	rts
	

; Routine to look up a screen in the table and return its associated window.
; Checks for existance of window. If window not in screen, returns frontmost
; window. WARNING: does not check for the existance of the screen (but does
; accept a NULL screen pointer, to which it returns NULL).
; Takes a0=^screen. Uses a5=^globals (uses ScreenTable and (locked) Intbase)
; Returns d0=^window. Trashes nil.
IndexScreenTable:
	pushm.l	a0-a1/d1
	bsr	FindScreenTuple
	tst.l	d0
	  beq.s	  .end	; Exit with d0=null if can't find screen
	move.l	d0,a1
	move.l	Tuple_Window(a1,d1.w),d0
	move.l	sc_FirstWindow(a0),d1	; Get first window in screen
.windloop	beq.s	.closed	; Return front window if no more windows
	  cmp.l	  d1,d0	; Is this the window we seek?
	beq.s	.end	; Found window in screen: success
	  move.l	  d1,a1
	  move.l	  wd_NextWindow(a1),d1	; Else look at _next_ window
	bra.s	.windloop	; Keep looping 'til no more windows
.end	popm.l	a0-a1/d1
	rts
.closed	move.l	sc_FirstWindow(a0),d0	; Return the frontmost window
	bra.s	.end	; ...and exit

; This routine garbage-collects non-existant (closed) screens from the table.
; Takes nil; Uses a5=^globals (for ScreenTable and (locked) Intbase)
; Returns nil; Trashes nil.
; Uses a0=^this chunk; d0=tuple offset; a1=^last chunk; d1=last tuple offset
; a2=^first screen; d2=^search screen; a3=^this screen; d3=tuple count
; a6=^execbase
PruneScreenTable:
	pushm.l	a0-a3/a6/d0-d3
	move.w	ScreenTable+ST_LastTupleOffset(a5),d1
	move.l	Execbase,a6
	move.l	IntBase(a5),a2
	move.l	ib_FirstScreen(a2),a2
	lea	ScreenTable+MLH_HEAD(a5),a0
	move.l	ScreenTable+MLH_TAILPRED(a5),a1	; (It's okay: we check to see if the first node exists, and if _it_ exists, there must be a last node too.)
.nextchunk	  move.l	  MLN_SUCC(a0),a0	; Get next chunk after this
	  tst.l	  MLN_SUCC(a0)	; (Do it this way in case the successor node changes (is removed) while we have a cached next-node pointer, for example.)
	beq.s	.endchunks	; Exit if we are back to list header
	  moveq	  #Chunk_Data,d0	; Offset of actual data
	  moveq	  #TuplesPerChunk-1,d3	; Repeat TuplesPerChunk times
.tupleloop	    move.l	    Tuple_Screen(a0,d0.w),d2	; Get next tuple in chunk
	beq.s	.endchunks ; NULL screen occurs only at end of list
	    lea	    (a2),a3	; Get the first screen on the system list
.screenloop	      cmp.l	      a3,d2	; Is this the screen we are looking for?
	    beq.s	    .nexttuple	; If found, on to next tuple
	      cmp.w	      #0,a3	; End of screen list?
	      movea.l     sc_NextScreen(a3),a3	; (CCR unaffected)
	    bne.s	    .screenloop	; Stop looping at list end
	    bsr.s	    .removescrn
.nexttuple	    addq.w	    #Tuple_SIZEOF,d0	; Offset of next datum
	  dbra	  d3,.tupleloop	; Loop on TuplesPerChunk
	bra.s	.nextchunk	; Loop on chunks
.endchunks	move.w	d1,ScreenTable+ST_LastTupleOffset(a5)
	popm.l	a0-a3/a6/d0-d3
	rts

	; Assume this bit is called only when:
	; a Tuple is to be removed; there are chunks in the list;
	; list is consistant and correct (very important);
	; a1=^last chunk; d1=LastTupOffset;
	; a0=^chunk; d0=offset (from a0) of any non-NULL tuple in list
	; If these rules are obeyed, we won't deallocate current node.
.removescrn	tst.w	d1
	bne.s	.dataleft	; \ If LastTupleOffset==NULL, deallocate
	  pushm.l	  a0/d0	; / the last chunk
	  move.l	  LN_SUCC(a1),a0	; a0 <- tail of list
	  move.l	  LN_PRED(a1),a3	; a3 <- last chunk but one
	  move.l	  a0,LN_SUCC(a3)
	  move.l	  a3,LN_PRED(a0)
	  move.l	  #Chunk_SIZEOF,d0
	  just	  FreeMem
	  lea	  (a3),a1	; Now get the _new_ last chunk
	  move.l	  #Chunk_SIZEOF-Tuple_SIZEOF,d1
	  popm.l	  a0/d0
.dataleft	move.l	Tuple_Screen(a1,d1.w),Tuple_Screen(a0,d0.w)
	move.l	Tuple_Window(a1,d1.w),Tuple_Window(a0,d0.w)
	clr.l	Tuple_Screen(a1,d1.w)	;  | Replace this tuple with
	clr.l	Tuple_Window(a1,d1.w)	; /  the last one in the table
	subq.w	#Tuple_SIZEOF,d1
	rts

