Action at a distance is an anti-pattern in computer science in which behavior in one part of a program modifies operations in another part of the program. This anti-pattern should be avoided whenever possible, but if wielded carefully SMC can become a practical ally when writing Uxntal.
Uxntal Doors
The ability to treat instructions as data makes programs that write programs possible. Self-modifying code(SMC) is generally considered harmful, and is therefore not permitted in most modern computer architectures today.
A door is an allocation of local memory that can store state across vectors.
@routine ( -- i ) [ LIT &door $1 ] INCk ,&door STR JMP2r
Caching Doors
In most cases, SMC is used to cache data that would otherwise be difficult or slow to retrieve, like when writing a responsive application that would make frequent requests to a device.
In the following door, we are comparing the state of the mouse device between vector events, we could store the previous state in a zero-page variable, but keeping the value locally allows to reserve a byte from within the context where it is needed, and is faster by being inlined.
@on-mouse ( -> ) [ LIT2 &last $1 -Mouse/state ] DEI DUP ,&last STR EORk ?&changed POP2 BRK
Callback Doors
To chain operations across vectors, one might try passing the next operation pointer on the stack, but since we cannot be certain which vector will happen next, we can't expect a specific stack state between events. A safer way is to write the next operation directly into a door where it will be needed, ideally preserving the label scope.
@set-animation ( callback* -- ) ,&callback STR2 ;&run .Screen/vector DEO2 JMP2r &run ( -> ) [ LIT &time f0 ] INCk ,&time STR #00 EQU ?&done try-redraw BRK &done ( -> ) [ LIT2 &callback $2 ] JSR2 BRK
Depth-Punching Doors
Routines should try and avoid accessing stack values that are further than 2 or 3 shorts deep on either stacks, but sometimes it cannot be helped. In the following example, we want to run a function over each value of a 2d array. Instead of juggling the stacks on each iteration to bring out the function pointer, it is often more efficient to write the function pointer across the nested loop.
@each-pixel ( fn* -- ) ,&fn STR2 #1000 &h STHk #2000 &x DUP STHkr [ LIT2 &fn $2 ] JSR2 INC GTHk ?&x POP2 POPr INC GTHk ?&h POP2 JMP2r
incoming uxntal memory uxntal devices