; ------------------------------------------------------------------------
; 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_matrix.inc: c'mon, what self-respecting text user interface library
; would it be without this?
;
; NOTE: hahah, with nearly all of my tests in various terminal software, this brings them
; ALL to their knees (read: 100% cpu by the displaying terminal).
;
; Some of it has to do with the unicode/kana characters, but still, you'd think a TEXT
; renderer would have a far more "high speed" symbol rate. The defaults that I have set
; in here do not cause complete havoc, which means that this component IS useful as-is,
; and does provide the desired effect. If you really want to see your terminal totally
; FREAK OUT, set the max stream parameter here to 1000 or so, make your terminal window
; a decent size, and prepare to have to forcibly terminate it. HAHAH
;
; Perhaps I should rename this to CPU Killer instead of tui_matrix, haha.
;
; as an aside, doing system call/library tracing with Terminal.app here suggests that
; it, like other terminal programs, seems to recalculate the width of an entire line
; when just one character in that line is updated. I suppose this makes sense for non
; fixed-width terminals, but for fixed-width, you'd sure think they'd optimize it.
; then again, I have not really encountered a situation until I built this text user
; interface library whereby their methods were rendered totally insufficient. I'd love
; to hear from the Apple Terminal.app developers on their opinion on this. Would be
; fun to crank these symbol rates right up, ha!
;
; hahah, this is more of a joke than anything, and I suppose as a demonstration of
; how flexible this text interface framework is, is an excellent example.
; otherwise, until terminal emulators decide to handle the unicode/drawing better
; is utterly useless, HA!
; settings for how this all comes together:
; our stream count, bahahaha, the windows original has this set to 1000, messy if you do that
; per the above commentary
tui_matrix_maxstream = 100
tui_matrix_backtrace = 30 ; original had 40 here
tui_matrix_leading = 10
tui_matrix_spacepad = 30
tui_matrix_r = 150
tui_matrix_g = 255
tui_matrix_b = 100
; the wikipedia says half-width kana, which is 63 characters starting at 0xff61,
; and latin alphanumerics.
; if we actually do that, WHOAAOAOAO, hahah, CPU killer indeed (not THIS process, your terminal's)
; so, if this setting is enabled, we go ahead and do the "wikipedia reference" version
; and send half-width kana AND latin alphanumerics
; if it is disabled, then we only send half-width kana (which is MUCH nicer to terminals)
; also note, cpukiller = 1 doesn't display correctly for Terminal.app, maybe I screwed up the
; math for the latin alphanumeric versus halfwidth kana, no idea, doesn't really matter anyway
; this thing even with cpukiller disabled absolutely hammers all my terminals
tui_matrix_cpukiller = 0
tui_matrix_incr = tui_matrix_r / 6
tui_matrix_incg = tui_matrix_g / 6
tui_matrix_incb = tui_matrix_b / 6
tui_matrix_incr_s = tui_matrix_r / 3 / 6
tui_matrix_incg_s = tui_matrix_g / 3 / 6
tui_matrix_incb_s = tui_matrix_b / 3 / 6
if used tui_matrix$vtable | defined include_everything
dalign
tui_matrix$vtable:
dq tui_matrix$cleanup, tui_matrix$clone, tui_matrix$draw, tui_object$redraw, tui_object$updatedisplaylist, tui_object$sizechanged
dq tui_matrix$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_matrix_intarr_size = tui_matrix_maxstream shl 2
tui_matrix_startx_ofs = tui_object_size + 0
tui_matrix_starty_ofs = tui_object_size + tui_matrix_intarr_size
tui_matrix_lastupdatey_ofs = tui_matrix_starty_ofs + tui_matrix_intarr_size
tui_matrix_streamspeed_ofs = tui_matrix_lastupdatey_ofs + tui_matrix_intarr_size
tui_matrix_origspeed_ofs = tui_matrix_streamspeed_ofs + tui_matrix_intarr_size
tui_matrix_streamstatus_ofs = tui_matrix_origspeed_ofs + tui_matrix_intarr_size
tui_matrix_streamcount_ofs = tui_matrix_streamstatus_ofs + tui_matrix_intarr_size
tui_matrix_timer_ofs = tui_matrix_streamcount_ofs + 8
tui_matrix_size = tui_matrix_timer_ofs + 4
if used tui_matrix$new | defined include_everything
; note: because this is mainly just a bell/whistle useless component, I only bothered with
; the default 100% width 100% height version. if you want it smaller/fixed size/different
; there are two ways to do that:
; 1) append this to a constrained parent, no real extra code required in here
; 2) add your own $new_ii/$new_id/$new_di/$new_dd/etc to here
;
; no arguments, returns a heap$alloc'd tui_matrix object all setup and ready to go
; NOTE: we _do_ fire our timer off here and get things underway, but since most will
; be immediately followed with an appendchild, this really isn't a problem
; cleanup takes care of killing our timer
;
falign
tui_matrix$new:
prolog tui_matrix$new
mov edi, tui_matrix_size
call heap$alloc
push rax
mov rdi, rax
movq xmm0, [_math_onehundred]
movq xmm1, [_math_onehundred]
call tui_object$init_dd
mov rsi, [rsp] ; timer_new wants object in rsi, time in edi
mov qword [rsi], tui_matrix$vtable
mov edi, 50 ; 50ms == 20fps
call epoll$timer_new
mov rdi, [rsp]
mov [rdi+tui_matrix_timer_ofs], rax ; store the timer object so we can blast it later
call tui_matrix$initial_fill
pop rax
epilog
end if
if used tui_matrix$cleanup | defined include_everything
; so, our timer is NEVER going to return a stop notice to the epoll layer, which means it will ALWAYS be valid
; throughout the lifetime of our object.
; what this means is that the only time our cleanup method is going to get called
; is when we, say, get removed from our parent... e.g. we will never be cleaned up from inside the timer handler
; itself inside the epoll object, so it is therefore SAFE to call epoll$timer_clear with our timer object
; without causing a circular nasty crash scenario (if our vcleanup were called from the timer handler itself,
; it too calls timer_clear, and heap$frees it, so this double could would be bad)... but we can be assured this
; doesn't happen simply by virtue of our timer not returning a "hey kill us please" return status.
falign
tui_matrix$cleanup:
prolog tui_matrix$cleanup
; since we don't have any dynamically allocated extra objects, all we have to make sure
; is that our timer object is already destroyed
mov rdi, [rdi+tui_matrix_timer_ofs]
call epoll$timer_clear
; nothign else need be done since we didn't dynamically allocate anything extra
epilog
end if
if used tui_matrix$initial_fill | defined include_everything
; single argument in rdi: our tui_matrix object
falign
tui_matrix$initial_fill:
prolog tui_matrix$initial_fill
mov dword [rdi+tui_matrix_streamcount_ofs], 0
mov ecx, tui_matrix_maxstream
xor eax, eax
lea rdx, [rdi+tui_matrix_streamstatus_ofs]
calign
.clearloop:
mov dword [rdx], eax
add rdx, 4
sub ecx, 1
jnz .clearloop
; do an initial fill of our entire space similar to what tui_background$fill does
mov eax, dword [rdi+tui_width_ofs]
mov ecx, dword [rdi+tui_height_ofs]
test eax, eax
jz .allgood
test ecx, ecx
jz .allgood
mul ecx
mov rdx, rdi
shl rax, 2
sub rsp, 16
mov [rsp], rdi
mov [rsp+8], rax
mov rdi, [rdx+tui_text_ofs]
test rdi, rdi
jz .bailout
mov esi, ' '
mov rdx, rax
call memset32
mov rcx, [rsp]
mov rdi, [rcx+tui_attr_ofs]
ansi_colors esi, 'green', 'black'
mov rdx, [rsp+8]
call memset32
add rsp, 16
epilog
calign
.bailout:
add rsp, 16
epilog
calign
.allgood:
epilog
end if
if used tui_matrix$clone | defined include_everything
; single argument: rdi == source tui_matrix to clone/make a copy of
; returns new tui_matrix copy in rax, NOTE: we don't ACTUALLY clone
; we just return a brand new one, cloning since there is no useful
; state information would be silly
falign
tui_matrix$clone:
prolog tui_matrix$clone
call tui_matrix$new
epilog
end if
if used tui_matrix$draw | defined include_everything
; single argument in rdi: our tui_matrix object
falign
tui_matrix$draw:
prolog tui_matrix$draw
push rdi
call tui_matrix$initial_fill
pop rdi
mov rsi, [rdi]
call qword [rsi+tui_vupdatedisplaylist]
epilog
end if
if used tui_matrix$createdestroy | defined include_everything
; single argument in rdi: our tui_matrix object
falign
tui_matrix$createdestroy:
prolog tui_matrix$createdestroy
cmp dword [rdi+tui_width_ofs], 0
je .nothingtodo
cmp dword [rdi+tui_matrix_streamcount_ofs], tui_matrix_maxstream
jae .dostops
lea rdx, [rdi+tui_matrix_streamstatus_ofs]
mov ecx, tui_matrix_maxstream
xor eax, eax
calign
.searchloop:
cmp dword [rdx], 0
jne .searchnext
; else, !streamstatus on this one
add dword [rdi+tui_matrix_streamcount_ofs], 1
mov dword [rdx], 1
lea rcx, [rdi+tui_matrix_lastupdatey_ofs]
mov dword [rcx+rax*4], -1
lea rcx, [rdi+tui_matrix_starty_ofs]
mov dword [rcx+rax*4], 0
push rdi rax
mov edi, [rdi+tui_width_ofs]
call rng$intmax
mov r8d, eax
pop rax rdi
lea rcx, [rdi+tui_matrix_startx_ofs]
mov dword [rcx+rax*4], r8d
push rdi rax
mov edi, 5
call rng$intmax
mov r8d, eax
pop rax rdi
lea rcx, [rdi+tui_matrix_origspeed_ofs]
mov dword [rcx+rax*4], r8d
lea rcx, [rdi+tui_matrix_streamspeed_ofs]
mov dword [rcx+rax*4], r8d
jmp .dostops
calign
.searchnext:
add eax, 1
add rdx, 4
sub ecx, 1
jnz .searchloop
calign
.dostops:
lea rdx, [rdi+tui_matrix_streamstatus_ofs]
mov ecx, tui_matrix_maxstream
xor eax, eax
calign
.stoploop:
cmp dword [rdx], 0
je .stopnext
lea r8, [rdi+tui_matrix_starty_ofs]
mov r9d, [r8+rax*4]
mov r10d, [rdi+tui_height_ofs]
add r10d, tui_matrix_backtrace
cmp r9d, r10d
jbe .stopnext
mov dword [rdx], 0
sub dword [rdi+tui_matrix_streamcount_ofs], 1
add eax, 1
add rdx, 4
sub ecx, 1
jnz .stoploop
epilog
calign
.stopnext:
add eax, 1
add rdx, 4
sub ecx, 1
jnz .stoploop
epilog
calign
.nothingtodo:
epilog
end if
if used tui_matrix$update | defined include_everything
; single argument in rdi: our tui_matrix object
falign
tui_matrix$update:
prolog tui_matrix$update
lea rdx, [rdi+tui_matrix_streamstatus_ofs]
mov ecx, tui_matrix_maxstream
xor eax, eax
calign
.updateloop:
cmp dword [rdx], 0
je .updatenext
lea r8, [rdi+tui_matrix_streamspeed_ofs]
cmp dword [r8+rax*4], 0
je .updateloop_caseone
sub dword [r8+rax*4], 1
add eax, 1
add rdx, 4
sub ecx, 1
jnz .updateloop
epilog
calign
.updateloop_caseone:
lea r8, [rdi+tui_matrix_starty_ofs]
add dword [r8+rax*4], 1
lea r8, [rdi+tui_matrix_origspeed_ofs]
mov r9d, dword [r8+rax*4]
lea r8, [rdi+tui_matrix_streamspeed_ofs]
mov dword [r8+rax*4], r9d
add eax, 1
add rdx, 4
sub ecx, 1
jnz .updateloop
epilog
calign
.updatenext:
add eax, 1
add rdx, 4
sub ecx, 1
jnz .updateloop
epilog
end if
if used tui_matrix$display | defined include_everything
; single argument in rdi: our tui_matrix object
falign
tui_matrix$display:
prolog tui_matrix$display
lea rdx, [rdi+tui_matrix_streamstatus_ofs]
mov ecx, tui_matrix_maxstream
xor eax, eax
calign
.streamloop:
cmp dword [rdx], 0
je .streamnext
lea r8, [rdi+tui_matrix_lastupdatey_ofs]
lea r9, [rdi+tui_matrix_starty_ofs]
mov r11d, dword [r9+rax*4]
cmp r11d, dword [r8+rax*4]
je .streamnext
mov dword [r8+rax*4], r11d
lea r8, [rdi+tui_matrix_startx_ofs]
mov r10d, dword [r8+rax*4]
; x is in r10d, y is in r11d
lea r8, [rdi+tui_matrix_origspeed_ofs]
mov r9d, [r8+rax*4]
; sos is in r9d
; note, we precalculate all of our necessary vars here for the different cases depending on y
sub rsp, 120
mov [rsp], rdi ; preserve our object
mov [rsp+8], rdx ; outer loop
mov [rsp+16], rcx ; outer loop
mov [rsp+24], rax ; outer loop
; calculate text/attr offsets for each of our three scenarios beforehand
mov ecx, [rdi+tui_height_ofs]
mov eax, [rdi+tui_width_ofs]
mov [rsp+32], rcx ; height
xor edx, edx
mul r11d ; eax now has y * width
add eax, r10d
shl eax, 2
mov [rsp+40], rax ; (y * width) + x in bytes
mov eax, [rdi+tui_width_ofs]
xor edx, edx
mov ecx, r11d
sub ecx, 1
mul ecx
add eax, r10d
shl eax, 2
mov [rsp+48], rax ; ((y-1) * width) + x in bytes
mov eax, [rdi+tui_width_ofs]
xor edx, edx
mov ecx, r11d
sub ecx, tui_matrix_backtrace
mul ecx
add eax, r10d
shl eax, 2
mov [rsp+56], rax ; ((y-backtrace) * width) + x
; store our text/attr offsets so we don't have to reload rdi over and over again
mov rsi, [rdi+tui_text_ofs]
mov [rsp+64], rsi
mov rsi, [rdi+tui_attr_ofs]
mov [rsp+72], rsi
; save our y (in r11) for the actual case checks
mov [rsp+80], r11
; next up: calculate our two different colors
xor edx, edx
mov eax, tui_matrix_incr
mul r9d
mov r8d, tui_matrix_r
sub r8d, eax ; r - sos * incR
xor edx, edx
mov eax, tui_matrix_incg
mul r9d
mov r10d, tui_matrix_g
sub r10d, eax ; g - sos * incG
xor edx, edx
mov eax, tui_matrix_incb
mul r9d
mov r11d, tui_matrix_b
sub r11d, eax ; b - sos * incB
; now, because we know this is the matrix and all, we aren't checking for r == g == b
; so we can just divide all of these by 43 and put them back into a 256 color for us
xor edx, edx
mov eax, r8d
mov ecx, 43
div ecx
mov r8d, eax ; (r - sos * incR) / 43
xor edx, edx
mov eax, r10d
mov ecx, 43
div ecx
xor edx, edx
mov ecx, 6
mul ecx
mov r10d, eax ; ((g - sos * incG) / 43) * 6
xor edx, edx
mov eax, r11d
mov ecx, 43
div ecx
xor edx, edx
mov ecx, 36
mul ecx
mov r11d, eax ; ((b - sos * incG) / 43) * 36
mov eax, r8d
add eax, r10d
add eax, r11d
add eax, 16
shl eax, 8
; update to make sure it is really black:
or eax, 0xe8
mov [rsp+88], rax ; initial color
; now do the same thing but with r/3 - sos*incR, and with incr_s instead
xor edx, edx
mov eax, tui_matrix_incr_s
mul r9d
mov r8d, tui_matrix_r / 3
sub r8d, eax ; r - sos * incR
xor edx, edx
mov eax, tui_matrix_incg_s
mul r9d
mov r10d, tui_matrix_g / 3
sub r10d, eax ; g - sos * incG
xor edx, edx
mov eax, tui_matrix_incb_s
mul r9d
mov r11d, tui_matrix_b / 3
sub r11d, eax ; b - sos * incB
; now, because we know this is the matrix and all, we aren't checking for r == g == b
; so we can just divide all of these by 43 and put them back into a 256 color for us
xor edx, edx
mov eax, r8d
mov ecx, 43
div ecx
mov r8d, eax ; (r/3 - sos * incR) / 43
xor edx, edx
mov eax, r10d
mov ecx, 43
div ecx
xor edx, edx
mov ecx, 6
mul ecx
mov r10d, eax ; ((g/3 - sos * incG) / 43) * 6
xor edx, edx
mov eax, r11d
mov ecx, 43
div ecx
xor edx, edx
mov ecx, 36
mul ecx
mov r11d, eax ; ((b/3 - sos * incG) / 43) * 36
mov eax, r8d
add eax, r10d
add eax, r11d
add eax, 16
shl eax, 8
; update to make sure it is really black:
or eax, 0xe8
mov [rsp+96], rax ; secondary color
; next up, we need two random characters, though we may only use one of them
if tui_matrix_cpukiller
; Terminal.app does _NOT_ like these, and doesn't render them correctly either...
mov edi, 366
call rng$intmax
mov rcx, rax
mov rdx, rax
sub rcx, 63
add rcx, 0x180 ; latin alphanumerics
add rdx, 0xff61 ; half-width kana
cmp rax, 63
cmovb rcx, rdx
; code in ecx is our random character
mov [rsp+104], rcx
; do it again
mov edi, 366
call rng$intmax
mov rcx, rax
mov rdx, rax
sub rcx, 63
add rcx, 0x180 ; latin alphanumerics
add rcx, 0xff61 ; half-width kana
cmp rax, 63
cmovb rcx, rdx
; code in ecx is our random character
mov [rsp+112], rcx
else
; It isn't particularly happy about these either, but way better than the cpukiller above
mov edi, 62
call rng$intmax
add rax, 0xff61 ; half-width kana only
mov [rsp+104], rax
; do it again
mov edi, 62
call rng$intmax
add rax, 0xff61
mov [rsp+112], rax
end if
; so we have done all our unpleasant calculations, even if we don't use them all
; so now we can check the y position, and draw the right goods
mov r11, [rsp+80] ; y
mov rcx, [rsp+32] ; height
cmp r11d, ecx
jae .checksecond
; else, load up text/attr/offset and set to first randomchar and our first color
mov r8, [rsp+64] ; text
mov r9, [rsp+72] ; attr
mov r10, [rsp+40] ; (y * width) + x in bytes
mov eax, dword [rsp+104] ; character
mov edx, dword [rsp+88] ; initial color
mov dword [r8+r10], eax
mov dword [r9+r10], edx
calign
.checksecond:
cmp r11d, ecx
ja .checkthird
test r11d, r11d
jz .checkthird
; else, load up text/attr/offset of y-1 and set to second randomchar and second color
mov r8, [rsp+64] ; text
mov r9, [rsp+72] ; attr
mov r10, [rsp+48] ; ((y-1)*width) + x in bytes
mov eax, dword [rsp+112] ; character
mov edx, dword [rsp+96] ; second color
mov dword [r8+r10], eax
mov dword [r9+r10], edx
calign
.checkthird:
cmp r11d, tui_matrix_backtrace
jb .nothird
sub r11d, tui_matrix_backtrace
cmp r11d, ecx
jae .nothird
; else, load up text/attr/offset of y-backtrace and set char to ' ' and colors to 'green', 'black'
mov r8, [rsp+64] ; text
mov r9, [rsp+72] ; attr
mov r10, [rsp+56] ; ((y-backtrace)*width) + x in bytes
ansi_colors edx, 'green', 'black'
mov dword [r8+r10], ' '
mov dword [r9+r10], edx
calign
.nothird:
; restore our regs and stack and keep going
mov rdi, [rsp]
mov rdx, [rsp+8]
mov rcx, [rsp+16]
mov rax, [rsp+24]
add rsp, 120
add eax, 1
add rdx, 4
sub ecx, 1
jnz .streamloop
epilog
calign
.streamnext:
add eax, 1
add rdx, 4
sub ecx, 1
jnz .streamloop
epilog
end if
if used tui_matrix$timer | defined include_everything
; single argument in rdi: our tui_matrix object
falign
tui_matrix$timer:
prolog tui_matrix$timer
push rdi
call tui_matrix$createdestroy
mov rdi, [rsp]
call tui_matrix$update
mov rdi, [rsp]
call tui_matrix$display
mov rdi, [rsp]
mov rsi, [rdi]
call qword [rsi+tui_vupdatedisplaylist]
pop rdi
xor eax, eax ; keep going
epilog
end if