HeavyThing - tui_effect.inc

Jeff Marrison

Table of functions

	; ------------------------------------------------------------------------
	; HeavyThing x86_64 assembly language library and showcase programs
	; Copyright © 2015-2018 2 Ton Digital 
	; Homepage: https://2ton.com.au/
	; Author: Jeff Marrison <jeff@2ton.com.au>
	;       
	; This file is part of the HeavyThing library.
	;       
	; HeavyThing is free software: you can redistribute it and/or modify
	; it under the terms of the GNU General Public License, or
	; (at your option) any later version.
	;       
	; HeavyThing is distributed in the hope that it will be useful, 
	; but WITHOUT ANY WARRANTY; without even the implied warranty of
	; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
	; GNU General Public License for more details.
	;       
	; You should have received a copy of the GNU General Public License along
	; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
	; ------------------------------------------------------------------------
	;       
	; tui_effect.inc: the base effect tui_object descendent for all our effects
	;
	; we support various kinds of effects: appendchild, appendbastard, removechild, removebastard, move, distort
	;
	; this base tui_effect layer doesn't do any actual effect, up to descendents of this to actually implement
	; the draw method
	;
	; what we do however is the initial setup that is common to all effects
	; and that is: turn the target into a particle list with no velocity/gravity/drag/etc
	; all with their current positions set to their real position
	;
	; we also provide the basic particle update/draw mechanism, also common to all
	;
	; so the slide effect for example, calls this layer's init, and then does a list$foreach over the particles
	; to set all their initial positions offscreen, and all their initial one-axis velocities to a positive #
	; and thats the entirety of the slide effects functionality, this layer takes care of the rest.
	;
	; each particle carries a target_x and target_y, and the effect will be considered done when all
	; particles reach their target _and_ minframes is reached (this way they can start and end at their target position if we want)
	;


if used tui_effect$vtable | defined include_everything

	; we are pretty much a standard tui_object, but we add a custom timer handler that calls draw
	; repeatedly, and takes care of the final end result depending on our constructor type
dalign
tui_effect$vtable:
	dq      tui_effect$cleanup, tui_object$clone, tui_object$draw, tui_object$redraw, tui_object$updatedisplaylist, tui_object$sizechanged
	dq      tui_effect$timer, tui_object$layoutchanged, tui_object$move, tui_object$setfocus, tui_object$gotfocus, tui_object$lostfocus
	dq      tui_object$keyevent, tui_object$domodal, tui_object$endmodal, tui_object$exit, tui_object$calcbounds, tui_object$calcchildbounds
	dq      tui_object$appendchild, tui_object$appendbastard, tui_object$prependchild, tui_object$contains, tui_object$getchildindex
	dq      tui_object$removechild, tui_object$removebastard, tui_object$removeallchildren, tui_object$removeallbastards
	dq      tui_object$getobjectsunderpoint, tui_object$flatten, tui_object$firekeyevent, tui_object$ontab, tui_object$onshifttab
	dq      tui_object$setcursor, tui_object$showcursor, tui_object$hidecursor, tui_object$click, tui_object$clicked

end if

tui_effect_parent_ofs = tui_object_size + 0		; the topmost tui_object parent of the target (just so we don't have to traverse each time to find it)
tui_effect_target_ofs = tui_object_size + 8		; the target of our effect itself
tui_effect_particles_ofs = tui_object_size + 16		; a list$new of tui_particle objects
tui_effect_forces_ofs = tui_object_size + 24		; a list$new of tui_force objects
tui_effect_time_ofs = tui_object_size + 32		; time in milliseconds of our timer fire (e.g. for 10fps, this is 100)
tui_effect_frames_ofs = tui_object_size + 40		; how many times the timer has fired
tui_effect_minframes_ofs = tui_object_size + 48		; the minimum number of frames before we contemplate being done, defaults to 0
tui_effect_type_ofs = tui_object_size + 56		; the type of effect we are, one of:
	tui_effect_type_appendchild = 0
	tui_effect_type_appendbastard = 1
	tui_effect_type_removechild = 2
	tui_effect_type_removebastard = 3
	tui_effect_type_move = 4
	tui_effect_type_distort = 5
tui_effect_movex_ofs = tui_object_size + 64		; if type == tui_effect_type_move, this is an int of where to move the target when we are done
tui_effect_movey_ofs = tui_object_size + 72		; "" for y
tui_effect_oncomplete_ofs = tui_object_size + 80	; if this is nonzero when our effect is complete, call this function
tui_effect_oncompletearg_ofs = tui_object_size + 88	; "", this will be rdi
							;      additional note: rsi will be set to the effect itself
tui_effect_injector_ofs = tui_object_size + 96		; convenience list for having a separate list of particles (default tui_effect doesn't make use of this
							; but does clean it up/create it)
tui_effect_alldone_ofs = tui_object_size + 104		; this gets set to 0 on init, but if you set it externally, the next timer cycle that runs will exit and clean itself up
tui_effect_timerptr_ofs = tui_object_size + 112		; this gets set to the epoll timer object upon init, and when cleanup happens, if this is nonzero, we call timer_clear
							; noting that for a "normal" finish (e.g. effect is complete, we clear it when we return 1 from the timer itself)
tui_effect_user_ofs = tui_object_size + 120
tui_effect_size = tui_object_size + 128




	; FORCES:
	; forces have an x/y position, a strength, active bool, an optional maximum distance (radius), an optional bounding box, and 32 bytes of userspace
	; to create "special" forces, hahah... the standard just kinda works like attract/repel with how much being inversely proportional to the distance
	; the target is away
	; 
	; note that this method makes a _point_ force, if you want flatline gravity in on or more directions, use the gravity fields of the particles instead
	; 
tui_force_x_ofs = 0		; double
tui_force_y_ofs = 8		; double
tui_force_strength_ofs = 16	; double
tui_force_active_ofs = 24	; bool
tui_force_radius_ofs = 32	; double
tui_force_bounds_ofs = 48	; bounds
	tui_force_bounds_ax_ofs = 48
	tui_force_bounds_ay_ofs = 52
	tui_force_bounds_bx_ofs = 56
	tui_force_bounds_by_ofs = 60
tui_force_update_ofs = 64	; pointer to the update function itself, useful if you want to create nonstandard forces, can be 0 for default, which doesn't change
tui_force_user_ofs = 72

tui_force_size = 104

	; we aren't creating any tui_force$new functions, up to user to initialize/heap$alloc them

if used tui_force$update | defined include_everything

	; argument in rdi: our tui_force object, argument in rsi: the tui_effect object we are attached to
	; if the update field contains a nonzero function pointer, we call it
	; otherwise, we don't do anything
	; NOTE: if the force is not active, update will still get called, this allows a force to, for example, turn itself
	; on and off over time, haha
	; if however, it is not active, when the particle update occurs, inactive forces are not applied to their values
falign
tui_force$update:
	prolog	tui_force$update
	cmp	qword [rdi+tui_force_update_ofs], 0
	je	.nothingtodo
	call	qword [rdi+tui_force_update_ofs]
	epilog
calign
.nothingtodo:
	epilog

end if


if used tui_force$apply | defined include_everything

	; argument in rdi: our tui_force object
	; NOTE: this relies on the particle's x being in xmm5, y being in xmm6, xvel being in xmm7, and yvel being in xmm8
	; if our force is applied, we only mess with xvel/yvel
falign
tui_force$apply:
	prolog	tui_force$apply
	pxor	xmm10, xmm10
	cmp	dword [rdi+tui_force_active_ofs], 0
	je	.nothingtodo
	; check to see if we have a bounding box first up
	cmp	qword [rdi+tui_force_bounds_bx_ofs], 0
	je	.checkradius
	; TODO: we probably need these values floored
	; else, we do have a nonzero bounding box, so convert this particle's x/y back into integers and check them
	cvtsd2si eax, xmm5				; TODO: change this to use r14/r15 across calls or something so this doesn't need to happen
	cvtsd2si ecx, xmm6
	cmp	eax, dword [rdi+tui_force_bounds_ax_ofs]
	jl	.nothingtodo
	cmp	ecx, dword [rdi+tui_force_bounds_ay_ofs]
	jl	.nothingtodo
	cmp	eax, dword [rdi+tui_force_bounds_bx_ofs]
	jge	.nothingtodo
	cmp	ecx, dword [rdi+tui_force_bounds_by_ofs]
	jge	.nothingtodo
calign
.doit:
	; else, it is inside the bounding box, so strength/distance applies
	; compute our distance first up
	movq	xmm0, [rdi+tui_force_x_ofs]
	movq	xmm1, [rdi+tui_force_y_ofs]
	subsd	xmm0, xmm5
	subsd	xmm1, xmm6
	mulsd	xmm0, xmm0
	mulsd	xmm1, xmm1
	addsd	xmm0, xmm1
	sqrtsd	xmm0, xmm0			; distance
calign
.doit_distancedone:
	; if our distance is zero, do nothing
	ucomisd	xmm0, xmm10
	je	.nothingtodo
	; get our strength
	movq	xmm1, [rdi+tui_force_x_ofs]
	movq	xmm2, [rdi+tui_force_y_ofs]
	movq	xmm3, [rdi+tui_force_strength_ofs]
	; deal with our x velocity modifications first, noting here we amplify the effect for x distance
	; because most all of my terminals are 3:1 width/height wise, and if we don't adjust them differently
	; the effect will be weird
	; testing is the only way to get these adjustments right


	; so, it is acting weird, because i am treating the xdistance and ydistance quite separately
	; the overall effect has to be based on the real distance

	; or, do we try it with ONLY the real distance... hmm the reason I separated it to begin with was
	; due to 3:1 browser... hmm

	; dx = x distance
	; dy = y distance

	; figure out strength / distance first
	divsd	xmm3, xmm0			; strength / distance
	; multiply that by dx to get our x modifier
	subsd	xmm1, xmm5			; x distance
	mulsd	xmm1, xmm3

	subsd	xmm7, xmm1
	; multiply that by dy to get our y modifier
	subsd	xmm2, xmm6			; y distance
	mulsd	xmm2, xmm3

	subsd	xmm8, xmm2
	epilog
calign
.checkradius:
	; if the radius is zero, then go ahead and do it, otherwise we need to check distance against radius
	movq	xmm9, [rdi+tui_force_radius_ofs]
	ucomisd	xmm9, xmm10
	je	.doit
	; otherwise, compute the distance, then check against xmm9
	movq	xmm0, [rdi+tui_force_x_ofs]
	movq	xmm1, [rdi+tui_force_y_ofs]
	subsd	xmm0, xmm5
	subsd	xmm1, xmm6
	mulsd	xmm0, xmm0
	mulsd	xmm1, xmm1
	addsd	xmm0, xmm1
	sqrtsd	xmm0, xmm0			; distance
	ucomisd	xmm0, xmm9
	jbe	.doit_distancedone
	epilog
calign
.nothingtodo:
	epilog

end if

if used tui_effect$cleanup | defined include_everything

	; single argument in rdi: our tui_effect object
falign
tui_effect$cleanup:
	prolog	tui_effect$cleanup
	push	rdi
	cmp	qword [rdi+tui_effect_timerptr_ofs], 0
	je	.notimerclear
	mov	rdi, [rdi+tui_effect_timerptr_ofs]
	call	epoll$timer_clear
	mov	rdi, [rsp]
	mov	qword [rdi+tui_effect_timerptr_ofs], 0
calign
.notimerclear:
	; we have to remove ourselves from our parent's bastard child list, free particles list and forces list
	; noting that for each particle, there may be more lists
	mov	rsi, rdi
	mov	rdi, [rsi+tui_effect_parent_ofs]
	call	tui_object$removebastard
	mov	rdx, [rsp]
	mov	rdi, [rdx+tui_effect_particles_ofs]
	mov	rsi, .particlefree
	call	list$clear
	mov	rdx, [rsp]
	mov	rdi, [rdx+tui_effect_particles_ofs]
	call	heap$free
	mov	rdx, [rsp]
	mov	rdi, [rdx+tui_effect_injector_ofs]
	mov	rsi, .particlefree
	call	list$clear
	mov	rdx, [rsp]
	mov	rdi, [rdx+tui_effect_injector_ofs]
	call	heap$free
	mov	rdx, [rsp]
	mov	rdi, [rdx+tui_effect_forces_ofs]
	mov	rsi, heap$free
	call	list$clear
	mov	rdx, [rsp]
	mov	rdi, [rdx+tui_effect_forces_ofs]
	call	heap$free

	; deal with our tui_object cleanup as well:
	mov	rdi, [rsp]
	call	tui_object$cleanup

	pop	rdi
	epilog
falign
.particlefree:
	push	rdi
	mov	rdi, [rdi+tui_particle_forces_ofs]
	mov	rsi, heap$free
	call	list$clear
	mov	rdx, [rsp]
	mov	rdi, [rdx+tui_particle_forces_ofs]
	call	heap$free
	pop	rdi
	call	heap$free
	ret

end if


if used tui_effect$init | defined include_everything

	; rdi == tui_effect object (descendent), esi == tui_effect_type, rdx == target, ecx == timer delay (ms), [ r8d == movex, r9d == movey, but only for type == tui_effect_type_move ]
	; if type != movex, r8 == oncomplete call, and if you want oncomplete functionality with type == move, set the oncomplete var yourself after init
	;     further, if type != movex, r9 == oncomplete call argument, again, set manually if you want it with type == move
	; this calls tui_object$init_defaults for our own effect object, then appends us as a bastard to the topmost parent of target
	; and then depending on what type of effect we are, if appending, sets visible = 0 for the target, and goes ahead and adds it
	; (which calculates its layout but doesn't actually draw it)
	; and if a remove/move/distort, doesn't do anything to target
	; and then turns target into a list of particles, and finally creates our epoll timer
	; no return
	; NOTE: we do not set our vtable (up to the caller), AND!! vtable must be set for us BEFORE ENTRY TO HERE.

	; ALSO NOTE NOTE NOTE: if we are an append effect, the target's parent must be set to the desired object that we are ultimately appending it to
	; but it must not yet be actually appended. the rest of the use cases assume that parent is valid/already set and that the child is already sweet/happy
falign
tui_effect$init:
	prolog	tui_effect$init
	mov	[rdi+tui_effect_target_ofs], rdx
	mov	[rdi+tui_effect_time_ofs], rcx
	mov	qword [rdi+tui_effect_frames_ofs], 0
	mov	qword [rdi+tui_effect_minframes_ofs], 0
	mov	[rdi+tui_effect_type_ofs], rsi
	mov	r10, r8
	xor	r11d, r11d
	cmp	esi, tui_effect_type_move
	cmove	r10, r11
	mov	[rdi+tui_effect_movex_ofs], r8		; if type isn't move, it is okay for these to be junk
	mov	[rdi+tui_effect_movey_ofs], r9
	mov	[rdi+tui_effect_oncomplete_ofs], r10
	; r9 may have been an oncompletearg, and if type == move, then this will be junk, but thats okay
	mov	[rdi+tui_effect_oncompletearg_ofs], r9
	push	rdi
	call	tui_object$init_defaults
	call	list$new
	mov	rdi, [rsp]
	mov	[rdi+tui_effect_particles_ofs], rax
	call	list$new
	mov	rdi, [rsp]
	mov	[rdi+tui_effect_forces_ofs], rax
	call	list$new
	mov	rdi, [rsp]
	mov	[rdi+tui_effect_injector_ofs], rax
	mov	qword [rdi+tui_effect_alldone_ofs], 0
	; now we need to establish the topmost parent of the target object, results are undefined if there is none, hahah, caller beware
	mov	rsi, [rdi+tui_effect_target_ofs]
	xor	rdx, rdx
calign
.findparent:
	mov	rdx, rsi
	mov	rsi, [rsi+tui_parent_ofs]
	test	rsi, rsi
	jnz	.findparent
	; rdx now has our topmost parent
	mov	[rdi+tui_effect_parent_ofs], rdx
	test	rdx, rdx
	jz	.kakked					; sanity only

	; next we need to set our preferred bounds...
	mov	eax, [rdx+tui_width_ofs]		; topmost parent's width
	mov	ecx, [rdx+tui_height_ofs]		; topmost parent's height
	mov	[rdi+tui_width_ofs], eax
	mov	[rdi+tui_height_ofs], ecx		; our width/height to same as topmost parents
	xor	eax, eax
	mov	[rdi+tui_absolutex_ofs], eax
	mov	[rdi+tui_absolutey_ofs], eax		; set our absolute x/y position to 0,0, relative to topmost parent (which is probably at actual 0,0 anyway)
	
	; append us as a bastard to the topmost parent, BEFORE we do anything
	mov	rsi, rdi
	mov	rdi, rdx
	call	tui_object$appendbastard		; hmmm, should we be using its vtable to call this?

	; bastards bounds setting does not fire a sizechanged event, so we'll do that to establish our initial buffers
	mov	rdi, [rsp]
	mov	rsi, [rdi]				; our vtable
	call	qword [rsi+tui_vsizechanged]

	; now we have proper buffers, and our draw method was called once
	mov	rdi, [rsp]
	cmp	dword [rdi+tui_effect_type_ofs], 2
	jae	.notappending
	; otherwise, we are appending the target to its parent, which was set before entry to the desired object we are appending it to
	mov	rsi, [rdi+tui_effect_target_ofs]
	mov	rdx, [rsi+tui_parent_ofs]
	mov	dword [rsi+tui_visible_ofs], 0		; make sure to set its visible property to 0 before we call the real appendchild
	mov	rcx, [rdx]				; the parent's vtable
	; what this does is, because tui_includeinlayout_ofs is still set to 1, will actually cause proper bounds to be calculated
	; for the target as if it were already appended (and it is)
	mov	rdi, rdx
	call	qword [rcx+tui_vappendchild]		; so now, our child has bounds (or better), but it is set to invisible, its draw was called
calign
.notappending:
	mov	rsi, [rsp]
	mov	rdi, [rsi+tui_effect_target_ofs]
	; so now, we need to flatten our target in order to turn it into particles
	mov	rsi, [rdi]	
	call	qword [rsi+tui_vflatten]
	test	rax, rax
	jz	.noparticles				; this better not happen, won't be much of an effect now will it? hahah
	; so we'll need to keep track of our current x, y, target's width, height, flat_text, flat_attr pointers, and our target list itself
	mov	rsi, [rsp]				; get back our object
	sub	rsp, 96
	mov	[rsp+8], rbx
	mov	[rsp+16], r12
	mov	[rsp+24], r13
	mov	[rsp+32], r14
	mov	[rsp+40], r15
	mov	[rsp+48], rax				; flat_text
	mov	[rsp+56], rdx				; flat_attr
	mov	rcx, [rsi+tui_effect_target_ofs]	; our target so we can get its width/height
	mov	r8d, [rcx+tui_width_ofs]
	mov	r9d, [rcx+tui_height_ofs]
	mov	r10d, [rcx+tui_bounds_ax_ofs]
	mov	r11d, [rcx+tui_bounds_ay_ofs]
	mov	[rsp+64], r8
	mov	[rsp+72], r9
	mov	[rsp+80], r10
	mov	[rsp+88], r11
	
	mov	rbx, [rsi+tui_effect_particles_ofs]	; our destination list
	mov	r12, rax
	mov	r13, rdx
	xor	r15d, r15d				; y
calign
.yloop:
	xor	r14d, r14d				; x
calign
.xloop:
	; tui_particle$new needs: char, attr, x, y
	mov	edi, dword [r12]
	mov	esi, dword [r13]
	add	r12, 4
	add	r13, 4
	mov	edx, r14d				; our current x/y in the target's context
	add	edx, dword [rsp+80]			; the real x/y in the screen's context
	mov	ecx, r15d
	add	ecx, dword [rsp+88]
	call	tui_particle$new
	mov	rdi, rbx
	mov	rsi, rax
	call	list$push_back
	add	r14d, 1
	cmp	r14d, dword [rsp+64]
	jb	.xloop
	
	add	r15d, 1
	cmp	r15d, dword [rsp+72]
	jb	.yloop

	; so now, all particles have been created, we can free our flat_text and flat_attr
	mov	rdi, [rsp+48]
	call	heap$free
	mov	rdi, [rsp+56]
	call	heap$free
	; and restore our callee-saves and stack
	mov	rbx, [rsp+8]
	mov	r12, [rsp+16]
	mov	r13, [rsp+24]
	mov	r14, [rsp+32]
	mov	r15, [rsp+40]
	add	rsp, 96
calign
.noparticles:
	mov	rsi, [rsp]				; our tui_effect object back
	mov	edi, dword [rsi+tui_effect_time_ofs]
	call	epoll$timer_new
	; specify that we want death and dismemberment to the epoll timer layer:
	mov	dword [rax+24], 1
	
	; in the event that we get torn down while our timer is still running, we need to be able to
	; clear the timer object
	pop	rdi
	mov	[rdi+tui_effect_timerptr_ofs], rax
	epilog
calign
.kakked:
	; programmer error
	breakpoint

end if

if used tui_effect$delay | defined include_everything
	; two arguments: rdi == our tui_effect object, esi == delay to set on all particles
falign
tui_effect$delay:
	prolog	tui_effect$delay
	mov	rdi, [rdi+tui_effect_particles_ofs]
	mov	edx, esi
	mov	rsi, .particledelay
	call	list$foreach_arg
	epilog
falign
.particledelay:
	add	dword [rdi+tui_particle_delay_ofs], esi
	ret

end if


if used tui_particle$new | defined include_everything

tui_particle_char_ofs = 0
tui_particle_attr_ofs = 8
tui_particle_orig_char_ofs = 16
tui_particle_orig_attr_ofs = 24
tui_particle_x_ofs = 32			; double
tui_particle_y_ofs = 40			; double
tui_particle_xvel_ofs = 48		; double
tui_particle_yvel_ofs = 56		; double
tui_particle_drag_ofs = 64		; double
tui_particle_gravity_ofs = 72		; double
tui_particle_hgravity_ofs = 80		; double
tui_particle_forces_ofs = 88		; list
tui_particle_targetx_ofs = 96		; double
tui_particle_targety_ofs = 104		; double
tui_particle_minx_ofs = 112		; double
tui_particle_maxx_ofs = 120		; double
tui_particle_miny_ofs = 128		; double
tui_particle_maxy_ofs = 136		; double
tui_particle_delay_ofs = 144		; int

tui_particle_size = 152

	; NOTE: that is a ridiculous number of fields per particle... but depending on the type of effect
	; that we are after, each and every one of them can and does come into play
	; min/max x and y for example, if the effect desired is only gravity, and particles accelerate
	; then without these, there is no way to "hit the target" without a ridiculously huge and nasty
	; bit of code to detect it, the default init makes the particles immovable by setting them all
	; to the initial x/y value.



	; four arguments: edi == char, esi == attr, edx == x, ecx == y
falign
tui_particle$new:
	prolog	tui_particle$new
	push	rdi rsi rdx rcx
	mov	edi, tui_particle_size
	call	heap$alloc
	push	rax
	call	list$new
	pop	r8
	mov	[r8+tui_particle_forces_ofs], rax
	pop	rcx rdx rsi rdi
	cvtsi2sd	xmm0, edx
	cvtsi2sd	xmm1, ecx
	mov	[r8+tui_particle_char_ofs], rdi
	mov	[r8+tui_particle_attr_ofs], rsi
	mov	[r8+tui_particle_orig_char_ofs], rdi
	mov	[r8+tui_particle_orig_attr_ofs], rsi
	movq	[r8+tui_particle_x_ofs], xmm0
	movq	[r8+tui_particle_y_ofs], xmm1
	movq	[r8+tui_particle_targetx_ofs], xmm0
	movq	[r8+tui_particle_targety_ofs], xmm1
	movq	[r8+tui_particle_minx_ofs], xmm0
	movq	[r8+tui_particle_maxx_ofs], xmm0
	movq	[r8+tui_particle_miny_ofs], xmm1
	movq	[r8+tui_particle_maxy_ofs], xmm1
	xor	ecx, ecx
	mov	edx, 1
	cvtsi2sd	xmm2, edx
	mov	[r8+tui_particle_xvel_ofs], rcx
	mov	[r8+tui_particle_yvel_ofs], rcx
	movq	[r8+tui_particle_drag_ofs], xmm2
	mov	[r8+tui_particle_gravity_ofs], rcx
	mov	[r8+tui_particle_hgravity_ofs], rcx
	; our "delay" counter ... if nonzero, particle updater will decrement and do nothing
	mov	[r8+tui_particle_delay_ofs], rcx
	mov	rax, r8
	epilog

end if

if used tui_particle$update | defined include_everything

	; two arguments: rdi == tui_particle object, rsi == tui_effect object that we belong to
	; NOTE NOTE NOTE: we use a "known-preserved across list$foreach_arg" r15 variable to indicate whether we are
	; NOT done... this gets used in the tui_effect timer loop to determine whether all particles have reached their
	; destination and in fact are done and we check against the tui_effect object's minimum frame count
	; in addition to our target destination to determine hwether we are really done or not.
falign
tui_particle$update:
	prolog	tui_particle$update
	push	rsi rdi
	cmp	dword [rdi+tui_particle_delay_ofs], 0
	jne	.delayset
	; [rsp] == our tui_particle, [rsp+8] == tui_effect object
	; first up, we have to update our own forces that are specific to this particle, in case they change/move/etc
	mov	rdx, rsi	; the tui_effect object
	mov	rsi, tui_force$update
	mov	rdi, [rdi+tui_particle_forces_ofs]
	call	list$foreach_arg

	; because list$foreach_arg doesn't mess with xmm regs, and we aren't going to be calling any memfuncs in between
	; these calls, we can safely load up this particles values ONCE, then call foreach on all forces
	; which can load their own values into OTHER xmms, and do the math that way
	; so, load up all our important values first, which are:
	mov	rdi, [rsp]
	movq	xmm5, [rdi+tui_particle_x_ofs]
	movq	xmm6, [rdi+tui_particle_y_ofs]		; forces need these to calculate distance
	movq	xmm7, [rdi+tui_particle_xvel_ofs]	; if a force acts upon us, it is going to mess with our x or y velocity
	movq	xmm8, [rdi+tui_particle_yvel_ofs]
	movq	xmm14, [rdi+tui_particle_targetx_ofs]
	movq	xmm15, [rdi+tui_particle_targety_ofs]

	; so now, we have to APPLY all forces, both our local ones, and the tui_effect globals, but these will per above
	; only mess with xmm7, xmm8
	; apply local forces (our own first)
	mov	rdi, [rdi+tui_particle_forces_ofs]
	mov	rsi, tui_force$apply
	call	list$foreach				; no arg necessary here
	; globals next
	mov	rcx, [rsp+8]
	mov	rdi, [rcx+tui_effect_forces_ofs]
	mov	rsi, tui_force$apply
	call	list$foreach
	; so now, if any of the forces messed with our xvel/yvel, we can now apply and then store our values back in
	; we have to apply our own movement math (velocities, gravities, drag)
	mov	rdi, [rsp]
	mov	rsi, [rsp+8]
	; load up the remaining values we need
	movq	xmm2, [rdi+tui_particle_drag_ofs]
	movq	xmm3, [rdi+tui_particle_gravity_ofs]
	movq	xmm4, [rdi+tui_particle_hgravity_ofs]

	mov	r8d, [rsi+tui_width_ofs]
	mov	r9d, [rsi+tui_height_ofs]

	; ok so, if our velocities are cruising along, but we are approaching our target
	; we need to make sure we don't skip past it... problem here is manifold, in that
	; we might be any direction away from our target
	; ... further, our actual velocity might be HUGE, and our distance might still be
	; well outside... there is no good way to do this
	;
	; so, the solution, though ugly, is to use four vars to each particle: min/max x and y

	movq	xmm10, [rdi+tui_particle_minx_ofs]
	movq	xmm11, [rdi+tui_particle_maxx_ofs]
	movq	xmm12, [rdi+tui_particle_miny_ofs]
	addsd	xmm5, xmm7		; x += xvel
	addsd	xmm6, xmm8		; y += yvel		hmm, should these be AFTER our velocity mods?
	movq	xmm13, [rdi+tui_particle_maxy_ofs]

	ucomisd	xmm5, xmm10		; is x < minx?
	jae	.minxokay
	movapd	xmm5, xmm10		; if so, x = minx
	jmp	.checky
calign
.minxokay:
	ucomisd xmm5, xmm11		; is x > maxx
	jbe	.checky
	movapd	xmm5, xmm11		; if so, x = maxx
	; copy of checky fallthrough
	ucomisd xmm6, xmm12		; is y < miny
	jae	.minyokay
	movapd	xmm6, xmm12		; if so, y = miny
	jmp	.checktarget
calign
.checky:
	ucomisd xmm6, xmm12		; is y < miny
	jae	.minyokay
	movapd	xmm6, xmm12		; if so, y = miny
	jmp	.checktarget
calign
.minyokay:
	ucomisd xmm6, xmm13		; is y > maxy
	jbe	.checktarget
	movapd	xmm6, xmm13		; if so, y = maxy
	; copy of checktarget fallthrough
	ucomisd	xmm5, xmm14
	jne	.notattarget
	ucomisd xmm6, xmm15
	je	.targetreached
	jmp	.notattarget
calign
.checktarget:
	ucomisd	xmm5, xmm14
	jne	.notattarget
	ucomisd xmm6, xmm15
	je	.targetreached

calign
.notattarget:
	mulsd	xmm7, xmm2		; xvel *= drag
	mulsd	xmm8, xmm2		; yvel *= drag

	addsd	xmm7, xmm4		; xvel += hgravity
	addsd	xmm8, xmm3		; yvel += gravity

	; put all our values back
	movq	[rdi+tui_particle_x_ofs], xmm5
	movq	[rdi+tui_particle_y_ofs], xmm6
	movq	[rdi+tui_particle_xvel_ofs], xmm7
	movq	[rdi+tui_particle_yvel_ofs], xmm8

	; hmm, rounding creates interesting problems here
	; we know that floor doesn't smash xmm5 or xmm6, so we can safely preserve them (CAVEAT EMPTOR)

	movapd	xmm0, xmm5		; x
	call	floor
	movapd	xmm5, xmm0
	movapd	xmm0, xmm6		; y
	call	floor
	movapd	xmm6, xmm0
	mov	rsi, [rsp+8]
	mov	rdi, [rsp]
	mov	r8d, [rsi+tui_width_ofs]
	cvtsd2si	ecx, xmm5	; x
	cvtsd2si	eax, xmm6	; y
	mov	r9d, [rsi+tui_height_ofs]
	
	; last but not least, we have to convert our x and y doubles into integers, and see if they are within bounds
	cmp	ecx, 0
	jl	.alldone
	cmp	eax, 0
	jl	.alldone
	cmp	ecx, r8d
	jge	.alldone
	cmp	eax, r9d
	jge	.alldone
	; otherwise, we appear to be inbounds

	xor	edx, edx
	mul	r8d			; y * width
	add	eax, ecx		; + x
	shl	eax, 2			; in bytes
	; eax now has our offset in bytes for our character

	; load up our character
	mov	ecx, [rdi+tui_particle_char_ofs]
	mov	edx, [rdi+tui_particle_attr_ofs]
	; buffers
	mov	r10, [rsi+tui_text_ofs]
	mov	r11, [rsi+tui_attr_ofs]
	mov	dword [r10+rax], ecx
	mov	dword [r11+rax], edx
	xor	r15d, r15d		; we didn't reach our target
	add	rsp, 16
	epilog
calign
.alldone:
	xor	r15d, r15d		; we didn't reach our target
	add	rsp, 16
	epilog

calign
.targetreached:
	; store back our modified goods, and stop all velocities, etc
	xorpd	xmm0, xmm0
	movq	[rdi+tui_particle_gravity_ofs], xmm0
	movq	[rdi+tui_particle_hgravity_ofs], xmm0
	movq	[rdi+tui_particle_xvel_ofs], xmm0
	movq	[rdi+tui_particle_yvel_ofs], xmm0
	movq	[rdi+tui_particle_x_ofs], xmm5
	movq	[rdi+tui_particle_y_ofs], xmm6
	; we still have to draw our target if it is inside a visible area, and due to the delayset goods, we make a copy of the draw code here
	movapd	xmm0, xmm5		; x
	call	floor
	movapd	xmm5, xmm0
	movapd	xmm0, xmm6		; y
	call	floor
	movapd	xmm6, xmm0
	
	mov	rdi, [rsp]
	mov	rsi, [rsp+8]
	mov	r8d, [rsi+tui_width_ofs]
	mov	r9d, [rsi+tui_height_ofs]
	
	; last but not least, we have to convert our x and y doubles into integers, and see if they are within bounds
	cvtsd2si	ecx, xmm5	; x
	cvtsd2si	eax, xmm6	; y
	cmp	ecx, 0
	jl	.targetreached_oob
	cmp	eax, 0
	jl	.targetreached_oob
	cmp	ecx, r8d
	jge	.targetreached_oob
	cmp	eax, r9d
	jge	.targetreached_oob
	; otherwise, we appear to be inbounds

	xor	edx, edx
	mul	r8d			; y * width
	add	eax, ecx		; + x
	shl	eax, 2			; in bytes
	; eax now has our offset in bytes for our character

	; load up our character
	mov	ecx, [rdi+tui_particle_char_ofs]
	mov	edx, [rdi+tui_particle_attr_ofs]
	; buffers
	mov	r10, [rsi+tui_text_ofs]
	mov	r11, [rsi+tui_attr_ofs]
	mov	dword [r10+rax], ecx
	mov	dword [r11+rax], edx

	add	rsp, 16
	epilog
calign
.targetreached_oob:
	; out of bounds, but our target was reached
	add	rsp, 16
	epilog

calign
.delayset:
	sub	dword [rdi+tui_particle_delay_ofs], 1

	; we still have to DRAW our particle, even if we didn't DO anything to it
	movq	xmm5, [rdi+tui_particle_x_ofs]
	movq	xmm6, [rdi+tui_particle_y_ofs]		; forces need these to calculate distance

	movapd	xmm0, xmm5		; x
	call	floor
	movapd	xmm5, xmm0
	movapd	xmm0, xmm6		; y
	call	floor
	movapd	xmm6, xmm0
	
	mov	rdi, [rsp]
	mov	rsi, [rsp+8]
	mov	r8d, [rsi+tui_width_ofs]
	mov	r9d, [rsi+tui_height_ofs]
	
	; last but not least, we have to convert our x and y doubles into integers, and see if they are within bounds
	cvtsd2si	ecx, xmm5	; x
	cvtsd2si	eax, xmm6	; y
	cmp	ecx, 0
	jl	.alldone
	cmp	eax, 0
	jl	.alldone
	cmp	ecx, r8d
	jge	.alldone
	cmp	eax, r9d
	jge	.alldone
	; otherwise, we appear to be inbounds

	xor	edx, edx
	mul	r8d			; y * width
	add	eax, ecx		; + x
	shl	eax, 2			; in bytes
	; eax now has our offset in bytes for our character

	; load up our character
	mov	ecx, [rdi+tui_particle_char_ofs]
	mov	edx, [rdi+tui_particle_attr_ofs]
	; buffers
	mov	r10, [rsi+tui_text_ofs]
	mov	r11, [rsi+tui_attr_ofs]
	mov	dword [r10+rax], ecx
	mov	dword [r11+rax], edx
	xor	r15d, r15d		; we didn't reach our target
	add	rsp, 16
	epilog

end if



if used tui_effect$timer | defined include_everything
	; single argument in rdi: our tui_effect object
falign
tui_effect$timer:
	prolog	tui_effect$timer
	; Burning Purpose: go through our forces list, and call each one's update first
	; then, clear our text and attribute buffer (hmm, do we need to clear the attribute buffer?)
	; then go through each particle and call its update with us as an argument
	cmp	dword [rdi+tui_effect_alldone_ofs], 0
	jne	.finalize		; does our final action and returns 1 which destroys us
	cmp	dword [rdi+tui_effect_frames_ofs], 0
	je	.firstframe
calign
.top:
	cmp	qword [rdi+tui_text_ofs], 0
	je	.nothingtodo
	; first, clear our buffers
	mov	eax, [rdi+tui_width_ofs]
	mov	ecx, [rdi+tui_height_ofs]
	test	eax, eax
	jz	.nothingtodo
	test	ecx, ecx
	jz	.nothingtodo	; these are sanityonly
	mul	ecx
	shl	eax, 2
	mov	edx, eax
	xor	esi, esi
	push	rdi rdx
	mov	rdi, [rdi+tui_text_ofs]
	call	memset32
	mov	rcx, [rsp+8]
	pop	rdx
	xor	esi, esi
	mov	rdi, [rcx+tui_attr_ofs]
	call	memset32
	; next up: run our forces update
	mov	rdx, [rsp]
	mov	rsi, tui_force$update
	mov	rdi, [rdx+tui_effect_forces_ofs]
	call	list$foreach_arg
	mov	rdx, [rsp]
	push	r15
	mov	r15d, 1
	mov	rsi, tui_particle$update
	mov	rdi, [rdx+tui_effect_particles_ofs]
	call	list$foreach_arg
	; we need to also call our updatedisplaylist so that our updates get flattened/rendered
	mov	rdi, [rsp+8]
	mov	rsi, [rdi]
	call	qword [rsi+tui_vupdatedisplaylist]
	mov	eax, r15d
	pop	r15
	pop	rdi
	add	dword [rdi+tui_effect_frames_ofs], 1		; increase our frame count
	test	eax, eax
	jnz	.finalize
	epilog
calign
.nothingtodo:
	mov	qword [rdi+tui_effect_timerptr_ofs], 0		; our timer gets cleared for us by the 1 ret
	mov	qword [rdi+tui_bounds_ofs], 0			; this shares the same spot as io_parent_ofs
	mov	eax, 1		; if we didn't have anything to do, we may as well stop our timer?
	epilog
calign
.finalize:
	; particle$update made it all the way through and left alldone set to 1 (or alldone was nonzero), which means we need to finish the process
	; if we are a remove effect type, now is the time to remove the target from its parent
	mov	qword [rdi+tui_effect_timerptr_ofs], 0		; our timer gets cleared for us by the 1 ret
	mov	qword [rdi+tui_bounds_ofs], 0			; this shares the same spot as io_parent_ofs
	cmp	dword [rdi+tui_effect_type_ofs], tui_effect_type_removechild
	je	.finalize_removechild
	cmp	dword [rdi+tui_effect_type_ofs], tui_effect_type_removebastard
	je	.finalize_removebastard
	; and return 1 so we get destroyed
	cmp	qword [rdi+tui_effect_oncomplete_ofs], 0
	je	.finalize_nocompletefunc
	push	rdi
	mov	rsi, [rdi+tui_effect_target_ofs]
	mov	dword [rsi+tui_visible_ofs], 1
	mov	rdx, [rsi]
	call	qword [rdx+tui_vupdatedisplaylist]		; make sure it redraws itself
	pop	rsi
	mov	rdi, [rsi+tui_effect_oncompletearg_ofs]
	call	qword [rsi+tui_effect_oncomplete_ofs]
	mov	eax, 1
	epilog
calign
.finalize_removechild:
	push	rdi
	mov	rsi, [rdi+tui_effect_target_ofs]
	mov	rdi, [rsi+tui_parent_ofs]
	mov	rdx, [rdi]
	call	qword [rdx+tui_vremovechild]
	mov	rdx, [rsp]
	mov	rdi, [rdx+tui_effect_target_ofs]
	mov	rsi, [rdi]
	call	qword [rsi+tui_vcleanup]
	mov	rdx, [rsp]
	mov	rdi, [rdx+tui_effect_target_ofs]
	call	heap$free
	; target is now destroyed
	pop	rdi
	cmp	qword [rdi+tui_effect_oncomplete_ofs], 0
	je	.finalize_deadchild_nocompletefunc
	mov	rsi, rdi
	mov	rdi, [rdi+tui_effect_oncompletearg_ofs]
	call	qword [rsi+tui_effect_oncomplete_ofs]
	mov	eax, 1
	epilog
calign
.finalize_deadchild_nocompletefunc:
	mov	eax, 1
	epilog
calign
.finalize_removebastard:
	push	rdi
	mov	rsi, [rdi+tui_effect_target_ofs]
	mov	rdi, [rsi+tui_parent_ofs]
	mov	rdx, [rdi]
	call	qword [rdx+tui_vremovebastard]
	mov	rdx, [rsp]
	mov	rdi, [rdx+tui_effect_target_ofs]
	mov	rsi, [rdi]
	call	qword [rsi+tui_vcleanup]
	mov	rdx, [rsp]
	mov	rdi, [rdx+tui_effect_target_ofs]
	call	heap$free
	; target is now destroyed
	pop	rdi
	cmp	qword [rdi+tui_effect_oncomplete_ofs], 0
	je	.finalize_deadchild_nocompletefunc
	mov	rsi, rdi
	mov	rdi, [rdi+tui_effect_oncompletearg_ofs]
	call	qword [rsi+tui_effect_oncomplete_ofs]
	mov	eax, 1
	epilog
calign
.finalize_nocompletefunc:
	; depending on what type of event we are determines what we need to do
	mov	rsi, [rdi+tui_effect_target_ofs]
	mov	dword [rsi+tui_visible_ofs], 1
	mov	rdx, [rsi]
	call	qword [rdx+tui_vupdatedisplaylist]		; make sure it redraws itself
	mov	eax, 1
	epilog
calign
.firstframe:
	cmp	dword [rdi+tui_effect_type_ofs], 2
	jb	.top
	; if we are removing, moving, distorting, then
	; we need to set the target's visible property to 0, but only here at the first frame of our effect
	mov	rsi, [rdi+tui_effect_target_ofs]
	mov	dword [rsi+tui_visible_ofs], 0
	jmp	.top

end if