@yoyodud3
Last updated on

Intro to GRAB Assembly

0
0m read

Recently, GRAB has added code blocks, which allow users to build programs with a block coding interface for writing assembly. This post introduces GRAB Assembly from a more technical point of view: writing code to copy into the game, and using the JSON Editor to write complex programs.

I first explain the structure of GRAB assembly, and explain a simple program before going into detail on the different parts of the language: registers, connections, operators, some commonly used patterns, and using macros.

Assembly is a low level programming language just above machine code. Instead of complex logic, each line is a very small, specific instruction.

Structure of a program

In GRAB Assembly, like most assembly languages, a program is a list of instructions. Each line represents one instruction. Instructions are executed from top to bottom unless control flow instructions (GOTO, IF, END) jump the ProgramCounter.

An instruction generally has the following form:

OPERATOR OPERAND OPERAND ...

For example:

SET R0 10

This instruction uses the SET operator to assign the constant value 10 to the register R0.

The operator defines what action is performed (for example, SET, ADD, MUL, or GOTO). The operands are the values the operator works on. These can be:

  • Working registers (R0-R7)
  • Object connections (Obj.Pos.X, Trg.Act, etc.)
  • Constant values (10, 0.5, -3)
  • Labels (loop, any string)

The number of operands depends on the operator. Some instructions take no operands, while others take one, two, or three. Most commonly, the first operand is the destination.

Comments are highly recommended to explain longer programs, they help make the program more readable and easier to update at a later date. Comments start with a semicolon (;). Everything on a line after the semicolon is removed when pasting code into GRAB.

SET R0 10 ; this text will be removed

A simple program

Before we dive into writing programs with GRAB Assembly, let’s break down a simple program.

Below is a program written in GRAB Assembly.

; infinitely move 'Obj' upwards

SET R0 10                      ; 3) speed to move

LABEL loop                     ; 5) label to jump back to
	ADD Obj.Pos.Y Obj.Pos.Y R0 ; 6) add the speed to the position
	SLEEP 0                    ; 7) sleep till next frame
GOTO loop                      ; 8) loop forever

Line 1, as well as the text after the semicolon (;) on each line, is a comment. It will be removed when pasting into GRAB, but is handy to explain what code is doing.

Line 2 is blank.

Line 3 sets the R0 register to the constant value 10.

Line 4 is blank.

Line 5 creates a LABEL called loop, which will be jumped to later to create an infinite loop.

Line 6 adds R0 (which is 10) to Obj.Pos.Y and puts the result into Obj.Pos.Y. Y is up and down; adding to it means Obj will move upwards.

Line 7 uses a very common trick of SLEEP 0, which halts the program until the next frame, which means that the loop will run only once per frame.

Line 8 uses GOTO to jump back to loop. This creates an infinite loop.

By using comments and indenting, this program becomes more easily readable, but in GRAB, is equivalent to the following:

SET R0 10
LABEL loop
ADD Obj.Pos.Y Obj.Pos.Y R0
SLEEP 0
GOTO loop

Working Registers

In GRAB Assembly the main way to store and manipulate data is through working registers. GRAB Assembly provides eight working registers, named R0 through R7. These registers can be read from and written to.

Registers have no fixed meaning and their purpose is entirely defined by how you use them.

; set R0 to a constant value
SET R0 10    ; R0 = 10

; perform addition using registers
ADD R0 R0 2  ; R0 = R0 + 2 -> 12

; use another register
SET R1 6     ; R1 = 6

; divide R0 by R1
DIV R0 R0 R1 ; R0 = R0 / R1 -> 2

Commonly, programs will:

  1. Load values from connections into registers.
  2. Perform calculations using registers.
  3. Write the final result back to a connection.

Because registers are limited (8), it’s good practice to reuse them carefully and to add comments that describe how each register is currently being used.

If you need more than 8 registers, you can use connections as extra places to store data. However, you should be aware that the object exists physically.

Connections

Connections can be added in GRAB’s selection menu. Select a code block, click Edit Connections, and select another object to add a connection to it.

When you add a connection to an object, you can connect the position, rotation, or active state, and can name the connection.

This creates a register such as:

Obj.Pos.X ; object position
Obj.Rot.Y ; object rotation
Trg.Act   ; trigger is active

Position and rotation are readable and writable. Active is read-only and only available for connections to triggers.

Connections behave just like working registers, but are connected to an object; when you modify the connection’s register, the object is modified; when the object is modified, the register is modified.

Connections can be used anywhere a register can, except for Act connections which cannot be modified in code.

NOTE: Rotations are calculated in Y -> X -> Z order. This means that, when the game computes how an object should be rotated based on the rotation values: first, the object is rotated around the Y axis; then the result of that rotation is rotated around the X axis; and finally the result of that rotation is rotated around the Z axis. So changing one rotation axis can affect how subsequent rotations behave.

Operators

InstructionOp 1Op 2Op 3Description
SETDSTSRCDST = SRC
SWAPABswap A and B
ADDDSTABDST = A + B
SUBDSTABDST = A - B
MULDSTABDST = A * B
DIVDSTABDST = A / B
MODDSTABDST = A % B
FLOORDSTSRCDST = ⌊SRC⌋
SQRTDSTSRCDST = √SRC
EQUALDSTABDST = A == B
LESSDSTABDST = A < B
GREATERDSTABDST = A > B
ANDDSTABDST = A && B
ORDSTABDST = A || B
NOTDSTSRCDST = !SRC
SINDSTSRCDST = sin(SRC)
COSDSTSRCDST = cos(SRC)
ATAN2DSTABDST = angle to (B,A)
LABELLABELdefine a label
GOTOLABELjump to a label
IFREGLABELjump to label if REG != 0
ENDhalt the program
RANDDSTNDST = random 0 to N - 1
SLEEPMSsleep for MS

Data Operators

Data operators are simple operators that move data in registers.

SET

The SET operator takes two operands: a destination register (DST), and a source value (SRC). It copies the source’s value into the destination.

  • DST - A writable register
  • SRC - A register or constant value
SET R0 10 ; set R0 to 10
SET R1 R0 ; set R1 to R0
SWAP

The SWAP operator takes two register operands (A, and B), and swaps their values.

  • A - A writable register
  • B - A writable register
SWAP R0 R1 ; swap the values of R0 and R1

Arithmetic Operators

Arithmetic operators take three operands: a destination register (DST), and two register or value operands (A, and B). They set the destination to the result of A and B.

  • DST - A writable register
  • A - A register or constant value
  • B - A register or constant value

The operators work as follows:

  • ADD will add two values (A + B)
  • SUB will subtract two values (A - B)
  • MUL will multiply two values (A * B)
  • DIV will divide two values (A / B)
  • MOD will modulo two values (remainder) (A % B)
ADD R0 10 3 ; R0 = 10 + 3 -> 13
SUB R0 10 3 ; R0 = 10 - 3 -> 7
MUL R0 10 3 ; R0 = 10 * 3 -> 30
DIV R0 10 3 ; R0 = 10 / 3 -> 3.333...
MOD R0 10 3 ; R0 = 10 % 3 -> 1
ADD R0 R0 10 ; R0 = R0 + 10
ADD R0 R1 R2 ; R0 = R1 + R2

NOTE: When using DIV, be careful not to divide by zero, which will error the program.

Other Math Operators

FLOOR

The FLOOR operator takes two operands: a destination register (DST), and a source value (SRC). It rounds down the source’s value into the destination.

  • DST - A writable register
  • SRC - A register or constant value
; R1 is 2.6
FLOOR R0 R1 ; round R1 down into R0
; R0 is 2
SQRT

The SQRT operator takes two operands: a destination register (DST), and a source value (SRC). It puts the square root of the source’s value into the destination.

  • DST - A writable register
  • SRC - A register or constant value
SQRT R0 25 ; set R0 to the square root of 25
; R0 is 5
SIN

The SIN operator takes two operands: a destination register (DST), and a source angle (SRC). It puts the sine of the source’s value as an angle in degrees into the destination.

  • DST - A writable register
  • SRC - A register or constant value
SIN R0 90 ; set R0 to the sine of 90
; R0 is 1
COS

The COS operator takes two operands: a destination register (DST), and a source angle (SRC). It puts the cosine of the source’s value as an angle in degrees into the destination.

  • DST - A writable register
  • SRC - A register or constant value
COS R0 90 ; set R0 to the cosine of 90
; R0 is 0
Trigonometry basics

If you are unfamiliar with sine and cosine, I’ll go over the fundamentals of what they are in the context of the SIN and COS instructions.

Sine and cosine are ways of turning an angle into numbers that describe direction. Imagine standing at the centre of a circle and pointing in some direction: COS tells you how far in the X direction that angle points, while SIN tells you how far in the Y direction it points. If the point always stays on the circle, these values smoothly range between -1 and 1 as the angle changes.

Using the diagram below, drag the white dot around the circle. The position of the dot directly matches the yellow line, which represents the angle SRC. The resulting SIN is the length of the red line from the dot down to 0 on the Y axis, and COS is the length of the blue line from the dot across to 0 on the X axis.

NOTE: All trigonometric operators in GRAB Assembly use degrees, not radians.

SRC = 45 SIN = 0.71 COS = 0.71
ATAN2

The ATAN2 operator takes two operands: a destination register (DST), a source y (A), and a source x (B). It sets the destination to the angle from (0, 0) to (B, A). You can think of it as reversing SIN and COS to get the original angle.

NOTE: ATAN2 uses (y, x), not (x, y).

  • DST - A writable register
  • A - A register or constant value
  • B - A register or constant value
ATAN2 R0 1 0 ; set R0 to atan2 of 1 and 0
; R0 is 90

Comparison Operators

Comparison operators take three operands and set the first to the result of the second and the third.

  • DST - A writable register
  • A - A register or constant value
  • B - A register or constant value

Comparison operators will always set the destination register to either 0 or 1, where 1 (and any other non 0 value) means the expression is true, and 0 means it is false. This means they work directly with IF, which checks if a register is not 0.

  • EQUAL Checks if A equals B
  • LESS Checks if A is less than B
  • GREATER Checks if A is greater than B
  • AND True only if both are true
  • OR True if either is true
  • NOT Flips true/false
EQUAL R0 1 2   ; R0 = (1 == 2) -> 0
LESS R0 1 2    ; R0 = (1 <  2) -> 1
GREATER R0 1 2 ; R0 = (1 >  2) -> 0
AND R0 1 0     ; R0 = (1 != 0 and 0 != 0) -> 0
OR R0 1 2      ; R0 = (1 != 0 or  0 != 0) -> 1
NOT R0 1       ; R0 = (!1) -> 0

NOTE: All values in GRAB Assembly are stored as floating point numbers. Because of this, arithmetic can introduce small precision errors (e.g., 0.1 + 0.2 = 0.30000000000000004), meaning results are often not exact. When comparing values, keep this in mind, especially with EQUAL. Only rely on equality when you know the values are set exactly.

For example, if you want to check whether an object has moved from 0 to 0.1, instead of checking whether the value is EQUAL to 0.1, check whether it is GREATER than 0.05.

Control Flow Operators

Control flow operators allow for jumping around the code to create loops, conditional code, or functions.

For example, a loop can be created that makes an object unable to pass below 0 height.

LABEL loop ; infinite loop

	GREATER R0 Obj.Pos.Y 0 ; if Y > 0
	IF R0 dont_limit       ; dont limit

		; set Y to 0       ; else ( <= 0 )
		SET Obj.Pos.Y 0

	LABEL dont_limit

GOTO loop

While I prefer this kind of pattern of having reversed conditions, (if not less than 0), and jumping over logic, others may prefer to have the conditional code separated more like a function.

This code is equivalent to the previous.

LABEL loop ; infinite loop

	LESS R0 Obj.Pos.Y 0 ; if Y < 0
	IF R0 limit         ; limit
	LABEL after_limit

GOTO loop

; limit function
LABEL limit
	; set Y to 0
	SET Obj.Pos.Y 0
	GOTO after_limit
LABEL

Labels a line in the code with a name.

LABEL loop
GOTO

GOTO jumps to a label.

GOTO loop
IF

IF will jump to the label only if REG is not zero.

SET R0 1
IF R0 func ; jump to 'func' if R0 is 1
END

Halts the code.

END

Misc Operators

RAND

RAND sets a register to a random number from 0 to N-1.

RAND R0 10
; R0 could be anything from 0 to 9
SLEEP

SLEEP pauses execution for a given amount of time in milliseconds.

TIP: SLEEP 0 will delay execution until the next frame.

SLEEP 1000 ; sleep for 1 second

Special Registers

Special registers are managed by the runtime and expose certain information about the current state of the program.

ProgramCounter

ProgramCounter is the index of the current instruction being read. The line number - 1. You write to it to jump to a specific line number, which, coupled with reading and saving it, can allow the implementation of callable functions.

NOTE: At the time of writing this, ProgramCounter is not yet accessible, but should be in the near future.

DeltaTime

DeltaTime stores the time since the last frame. DeltaTime should be used to make moving objects smooth and consistent by multiplying movement by the DeltaTime to ensure the frame rate doesn’t affect the speed.

Others
  • Halt whether the program is halted
  • HaltFrame whether execution is halted for this frame (SLEEP 0)
  • SleepTimer remaining time until sleep completes

Common Patterns

Infinite loop with sleep 0

Commonly, you will want to have something run every frame. You can achieve this by creating a LABEL and a GOTO that wrap around code to run infinitely.

It is also important to include a SLEEP 0 at the end of the loop to ensure the loop only runs once per frame. Without SLEEP 0, the program will run up to 500 instructions each frame, which is usually not what you want.

LABEL loop
	; code here
	SLEEP 0
GOTO loop

DeltaTime

As explained above, DeltaTime stores the time since the last frame.

Generally, when moving something, you want it to be at a constant rate.

If you create a simple movement loop:

LABEL loop
	ADD Obj.Pos.X Obj.Pos.X 0.1
	SLEEP 0
GOTO loop

The object will move 0.1m per frame.

If you have a stable frame rate, that will be 75 fps * 0.1m = 7.5m/s. However, if you are lagging, then the object will move slower because there are less frames.

The way to counteract this to make the movement appear smooth, and be consistent locally and across multiplayer, is to multiply the movement by DeltaTime.

LABEL loop
	MUL R0 0.1 DeltaTime
	ADD Obj.Pos.X Obj.Pos.X R0
	SLEEP 0
GOTO loop

Macros

The JSON Editor has a simple but very powerful extra tool on top of GRAB Assembly: preprocessor macros that allow for generating far more complex programs.

MacroArg 1Arg 2Arg 3Arg 4Description
#FORvarstartstopstepfor (i = start; i <= stop; i += step)
#IFAopBif (A op B)
#ENDend scope

FOR Macro

#FOR takes a variable name, and a start, stop, and step. It loops from start to stop inclusively, incrementing by step each loop. step can be omitted and defaults to 1.

IF Macro

#IF takes two values with an operator in the middle. Valid operators are:

  • == A equals B
  • != A does not equal B
  • > A is greater than B
  • >= A is greater than or equal to B
  • < A is less than B
  • <= A is less than or equal to B
; is a 10
#IF a == 10
#END

; is x less than y
#IF x < y
#END

Macro variables

#FOR macros will create variables with the first argument.

To use these variables, you can write #name anywhere inside the loops scope and it will be substituted.

The # is not needed when using it in subsequent macros. Only when in GRAB Assembly code.

; loop 0 to 9
#FOR n 0 9
	; sets all objects named Obj0 to Obj9 X positions to 0 to 9
	SET Obj#n.Pos.X #n
#END

Macro Arguments

Any value operands can be either a number (1, 1.1), a variable (a, x, i), or a simple expression (a+b, 1*2, a/2). Valid ‘simple expressions’ are 2 values with one of +, -, *, /, or %, and cannot include a space.

; note: real code would need #END
#FOR a 0 10 1
#FOR a b c-1 d+1
#IF 1*10 == a*b
Example
SET R7 21 ; to get 0 to 20
SET R6 10 ; then - 10 to get -10 to 10

#FOR n 1 3
	; 0  - 20
	RAND R0 R7
	RAND R1 R7
	RAND R2 R7

	#IF n == 3
		SET R0 10 ; 10 - 10 means always 0
	#END

	; -10 - 10
	SUB Obj#n.Pos.X R0 R6
	SUB Obj#n.Pos.Y R1 R6
	SUB Obj#n.Pos.Z R2 R6
#END

This code uses #FOR to duplicate code that sets the X, Y, and Z of an object to a random value -10 through 10. The for loop creates a variable n at 1 and loops up to 3. Step is omitted, so it is 1. For each loop it sets Obj#n.Pos.X where #n will be replaced with the current value of n. e.g., Obj1.Pos.X.

There is also an #IF that runs when n == 3. This means each loop will check that condition, and if it is true, will add the SET R0 10 line, which forces the object’s X position to be 0.

The resulting GRAB Assembly code will set 3 objects to random -10 to 10 positions, but object 3’s X will always be 0:

SET R7 21            ; the constants from the top
SET R6 10
RAND R0 R7           ; loop where n is 1
RAND R1 R7
RAND R2 R7
SUB Obj1.Pos.X R0 R6
SUB Obj1.Pos.Y R1 R6
SUB Obj1.Pos.Z R2 R6
RAND R0 R7           ; loop where n is 2
RAND R1 R7
RAND R2 R7
SUB Obj2.Pos.X R0 R6
SUB Obj2.Pos.Y R1 R6
SUB Obj2.Pos.Z R2 R6
RAND R0 R7           ; loop where n is 3
RAND R1 R7
RAND R2 R7
SET R0 10            ; n is 3 so this part is here
SUB Obj3.Pos.X R0 R6
SUB Obj3.Pos.Y R1 R6
SUB Obj3.Pos.Z R2 R6

You are now enlightened

Thanks for reading! If you found this guide useful, share it around! If you have some feedback, let me know! I appreciate it :)

Also, if you are here at the time of publishing this, Merry christler or whatever.

View Source