[Source] [Multithreaded] Assembly-like Code Compiler / Runner

Binsk

Member
Alright, so I'm not really sure where to put this since we don't have an "Open Source" section anymore.

If you don't like reading, here is your download.
Optionally, the source for the external runner.

What Is This:
This is my take on having dynamically added code to my projects post-compile. I essentially designed an assembly-like language that you can program in. I also wrote a compiler for the code in GML and an internal runner (in GML) and external multi-threaded runner (in C++) so that the code can actually be executed.

The language is essentially formed out of roughly 50-60 commands, registers, and IO ports. You can read data into registers from a number of IO ports, manipulate it, then push it back out to GameMaker.

The compiler converts it into a single GameMaker buffer which can then be executed manually or through the runner. The internal runner will limit execution times to attempt to keep the framerate at your desired level while the external runner will run tasks on however many threads you specify.

Why Is This:
I have been contemplating an idea like this for some time. I finally had a free day with little work to do so I spent most of the day whipping this up. I have since added a little here and there, but most of this was scrapped together in a day. This in mind, there is a lot here that could be handled cleaner and in a more optimized fashion. I took some short-cuts so that I could finish in the amount of time that I had.

The external runner is a new idea in the hopes of making this more beneficial by actually adding something significant to GameMaker, that being multi-threaded operations.

What Kind Of Functionality:
The language is mostly designed for numbers and math, albeit there are some simple pointer / buffer operations where you can write strings into them.

You have basic math such as addition, division, sqrt, etc. You have bitwise operators, a fractional function as well as several system commands. System commands consist of things like moving data in/out of registers and IOs, storing the current command byte and jump commands. You can do quite a lot with this language and if you want an example then take a look down below for a 2D simplex noise generation script written in this language.

Does This Actually Compile To Native?
No. This does not actually compile down to native computer code. It "compiles" down to a GameMaker buffer where the code is interpreted. It is also not a true assembly language. It does not access actual registers (and in fact things like registers and strings are still handled like a high-level language would), it merely shares the terms. It was a balancing act between simplicity and practicality. I tried to keep it simple to program but still functional enough to be practical to use.

All this said, it is still usable as it remains fairly fast.

Are You Done Yet?
If you made it this far you must at least be somewhat interested. Please, download and take a gander. Play with it, see what you can do. If you have suggestions, additions and/or critiques please let me know! I'd love some feedback.

NOTE: I included a DLL for the external runner for those on Windows. I made sure to only use standard C++ libraries, so if you have a different OS you can compile a version using the provided source code. You shouldn't have to change any code minus perhaps the GMX macro for proper external library creation.

Example Code?

Code:
; Author:    Reuben Shea
; Date:        01-15-2018
;
; Description:    This script is written in BIC and will generate
;        a block of 2D simplex noise and store the results
;        in a buffer as a series of f64s between [-1..1]
;
; Notes:
;    -    Because values get copied when transfering between
;        io and reg ports we reserve some registers for the buffers.
;        This prevents slowing down the system with constant re-copying
;        of large buffers.
;
;    -    Macros are, obviously, not required. However, it makes
;        things significantly more readable.

; -- REGISTERS -- ;
;
; 0-4:    [reserved]     mathematical operations
; 5:    [reserved]     hash buffer
; 6:    [reserved]     gradient buffer
; 7:    [reserved]     output buffer
; 8:    [float]        current x-coord (literal)
; 9:    [float]        current y-coord (literal)
; 10-18:[misc]
; 19    [int]        current array x-coord
; 20    [int]        current array y-coord

; -- IO PORTS -- ;
;
; 0:    [ptr]    Output buffer (as f64s)
; 1:    [ptr]    Hash buffer (as u8s)
; 2:    [ptr]    Gradient buffer (as f64s)
; 3:    [float]    Starting x-coord (literal)
; 4:    [float]    Starting y-coord (literal)
; 5:    [float]    Offset scalar (literal, between coords)
; 6:    [int]     Chunk width (in pixels)
; 7:    [int]     Chunk height (in pixels)

; -- MACROS -- ;
    ; IO:
!define    io_output    0
!define io_hash        1
!define io_grad        2
!define io_xstart    3
!define io_ystart    4
!define    io_scalar    5
!define io_width    6
!define io_height    7

    ; REG:
; I like defining macros for my math registers
; because you use low numbers often and this helps
; the registers stand out from integers.
!define    r0        0
!define r1        1
!define r2        2
!define r3        3
!define r4        4
!define reg_hash    5
!define reg_grad    6
!define reg_output    7
!define reg_x        8
!define reg_y        9
!define reg_ax        19
!define reg_ay        20

    ; OTHER:
!define f_skew        0.36603 ; Skew factor for the coordinates
!define f_unskew    0.21132
!define count_reg    20    ; Number of registers we will be using
!define count_io    8    ; Number of io ports we will be using

; -- STARTUP CODE -- ;
regioalloc    count_reg count_io    ; Allocate memory for our registers / io
sinr        io_hash    reg_hash    ; Start loading in our reserved register data
sinr        io_grad    reg_grad
sinr        io_xstart reg_x        ; Our current x-location for simplex
sinr        io_ystart reg_y        ; Our current y-location for simplex
rcpi        reg_ax 0
rcpi        reg_ay 0

    ; Allocate memory for our output buffer.
    ; Instead of passing in a buffer from GameMaker, we just generate it here.
sinr    6 r1    ; Load the width of our chunk into register 1
sinr    7 r2    ; Load in the height to reg 2
rcpi    r3 8    ; Write the integer 8 into reg 3
rmul    r1 r2    ; Multiply reg1 by reg2 and store in reg0
rmul    r0 r3    ; Multiply reg0 by reg3 and now we have the size we need for our buffer!
rpalloc reg_output r0    ; Allocate space for the buffer and store the pointer in reg_output (reg 7)

; -- MAIN LOOP BEGIN -- ;
.MAIN    ; We can name our jump-point anything. .MAIN seems appropriate

    ; NOTES:
    ; (Keeping notes can help keep track of registers!)
    ; (These will be important values at the end of this code segment)
    ; [int]        reg12     = x-skew [0..255]
    ; [int]        reg13     = y-skew [0..255]
    ; [float]    reg10     = x-unskew
    ; [float]    reg11     = y-unskew
    ; [float]    reg4     = unskew value
; Calculate skewed coordinates:
radd    reg_x reg_y
rcpf    r1 f_skew
rmul    r0 r1
rcpr    r1 r0        ; Store skew value in reg1

radd    reg_x r1    ; Calculate skewed x-pos
rftoi    r0        ; Simple way to floor a value; convert to int and back!
ritof     r0
rcpr    r2 r0        ; Store x-result in reg2

radd    reg_y r1    ; Calculate skewed y-pos
rftoi    r0
ritof    r0
rcpr    r3 r0        ; Store y-result in reg3

; Calculate unskew values (for later):
radd    r2 r3
rcpf    r4 f_unskew
rmul    r0 r4
rcpr    r4 r0        ; Store unskew value in reg4

rsub    r2 r4        ; Calculate x-unskew
rcpr    10 r0        ; Store x-unskew in reg10

rsub    r3 r4        ; Calculate y-unskew
rcpr    11 r0        ; Store y-unskew in reg11

; Limit our skewed x/y values to [0..255]
rcpi     r1 255

rftoi     r2        ; Convert our x-value to an int
rand    r0 r1        ; Perform a bitwise and
rcpr    12 r0        ; Store the new value

rftoi    r3        ; Perform the same with y-value
rand    r0 r1
rcpr    13 r0

; -- WORK OUT HASHED GRADIENT INDICES -- ;
    ; NOTES:
    ; [float]    reg10    = x-distance (from origin)
    ; [float]    reg11    = y-distance (from origin)
    ; [int]        reg14    = hash-value 1
    ; [int]        reg15    = hash-value 2
    ; [int]        reg16    = hash-value 3
    ; [float]    reg17    = x-distance (corner 2)
    ; [float]    reg18    = y-distance (corner 2)

; Calculate distance from cell origin:
rsub    reg_x 10    ; Subtract unskew-x from original x
rcpr    10 r0        ; Store result in reg10

rsub    reg_y 11    ; Subtract unskew-y from original y
rcpr    11 r0        ; Store result in reg11

; Calculate x vs y based on these distances:
; We get to do an if/else statement here!

rsub    10 11    ; Calculate x - y
jumppn .ifXLY    ; If our x - y > 0 then jump to .ifXLY (aka, x > y)
jump   .elseXLY ; If we didn't jump then it is false so jump to .elseXLY!
.ifXLY
    rcpi    r1 1    ; r1 will be our "x", so store a 1!
    rcpi    r2 0    ; r2 will be our "y", so store a 0!
    jump .finXLY    ; We want to skip the "else" so we jump past it
.elseXLY
    rcpi    r1 0    ; Same as above, r1 is our "x" and r2 our "y"!
    rcpi    r2 1
.finXLY

; Calculate distance for corner 2 down the road since it needs some data from here:
ritof    r1
rsub    10 r0
rcpf    r3 f_unskew
radd    r0 r3
rcpr    17 r0

ritof    r2
rsub    11 r0
radd    r0 r3
rcpr    18 r0

; Calculate hash 2
    ; NOTE: We calculate hash 2 first because it is the only one that
    ;     needs the values in r1 and r2 right now. This frees up the
    ;    registers as quickly as possible.
radd    r2 13        ; Our "y" to our skewed y
rpgb    reg_hash r0    ; Use the result as our hash index (this is not our final hash value)
radd    r0 r1        ; Add to our "x"
radd    r0 12        ; Add to our skewed x
rpgb    reg_hash r0    ; Grab the final hash value using our result as the index

    ; Now we need to perform hash % 12, this is how we call a function:
rcpr    r1 r0        ; Copy the hash into r1 for our function
rcpi    r2 12        ; Copy our modulo value into r2 for our function
rcpi    r3 6        ; Copy a byte offset to push our byte location past
            ; our jump function below
rstorloc        ; Store our current byte location in the code
radd    r0 r3        ; Add the byte offset [radd = 3 bytes, jump = 3 bytes]
jump .funMOD
rcpr    15 r1        ; At this point, our function is done and we store the result for real!

; Calculate hash 1
    ; This is essentially the same but with slightly simpler math. I
    ; will skip the comments here.
rpgb    reg_hash 13
radd    r0 12
rpgb    reg_hash r0

rcpr    r1 r0
rcpi    r2 12
rcpi    r3 6
rstorloc
radd    r0 r3
jump .funMOD
rcpr    14 r1

; Calculate hash 3
    ; Same as above, comments shall be skipped.
rcpi    r2 1
radd    13 r2
rpgb    reg_hash r0
radd    r0 r2
radd    r0 12
rpgb    reg_hash r0

rcpr    r1 r0
rcpi    r2 12
rcpi    r3 6
rstorloc
radd    r0 r3
jump .funMOD
rcpr    16 r1

; -- CALCULATE CORNER CONTRIBUTIONS -- ;
    ; NOTES:
    ; [float]    reg14    = corner-1 contribution
    ; [float]    reg15    = corner-2 contribution
    ; [float]    reg16    = corner-3 contribution

; Calculate corner 1
rcpf    r3 0.5
rmul    10 10        ; Square our x-distance
rcpr    r1 r0        ; Store result in reg1
rmul    11 11        ; Square our y-distance
rcpr    r2 r0        ; Store result in reg2
rsub    r3 r1
rsub    r0 r2

jumpnn .ifT0L0
jump .elseT0L0
.ifT0L0
    ; No contribution
    rcpf    14 0.0
    jump .finT0L0
.elseT0L0
    ; We are good to contribute. Let's calculate how much:
    rmul    r0 r0
    rmul    r0 r0    ; Lots of squaring...
    rcpr    r4 r0    ; Store this for now in r4

    ; Now we have to read our gradients, let's get the proper location
    rcpi    r0 16    ; Buffer of f64 and two values per "section", so compensate for data size
    rmul    r0 14    ; Multiply by our hash offset
    rcpr    r1 r0    ; Store our location in r1 (we have to re-use it shortly)
    rpgf    reg_grad r0
    rcpr    r2 r0    ; Copy our x-val into r2

    rcpi    r0 8
    radd    r1 r0    ; Shift to our next location
    rpgf    reg_grad r0
    rcpr    r3 r0    ; Copy our y-val into r3

    ; Time for a dot product!
    rmul    r2 10    ; x-coord
    rcpr    r1 r0    ; Store in reg1

    rmul    r3 11    ; y-coord
    radd    r1 r0    ; Add to x-coord

    rmul    r0 r4    ; Multiply by our stored value from the beginning
    rcpr    14 r0    ; And store the result, that's one corner down!
.finT0L0

; Calculate corner 2
rcpf    r3 0.5
rmul    17 17
rcpr    r1 r0
rmul    18 18
rcpr    r2 r0
rsub    r3 r1
rsub    r0 r2

jumpnn .ifT1L1
jump .elseT1L1
.ifT1L1
    ; No contribution
    rcpf    15 0.0
    jump .finT1L1
.elseT1L1
    ; We are good to contribute. Let's calculate how much:
    rmul    r0 r0
    rmul    r0 r0    ; Lots of squaring...
    rcpr    r4 r0    ; Store this for now in r4

    ; Now we have to read our gradients, let's get the proper location
    rcpi    r0 16    ; Buffer of f64, so compensate for data size
    rmul    r0 15    ; Multiply by our hash offset
    rcpr    r1 r0    ; Store our location in r1 (we have to re-use it shortly)
    rpgf    reg_grad r0
    rcpr    r2 r0    ; Copy our x-val into r2

    rcpi    r0 8
    radd    r1 r0    ; Shift to our next location
    rpgf    reg_grad r0
    rcpr    r3 r0    ; Copy our y-val into r3

    ; Time for a dot product!
    rmul    r2 17    ; x-coord
    rcpr    r1 r0    ; Store in reg1

    rmul    r3 18    ; y-coord
    radd    r1 r0    ; Add to x-coord

    rmul    r0 r4    ; Multiply by our stored value from the beginning
    rcpr    15 r0    ; And store the result, that's one corner down!
.finT1L1

; Calculate corner 3
; We have to calculate the distance for this corner first and store it in r17, r18
rcpf    r2 1.0
rcpf    r3 2.0
rcpf    r4 f_unskew
    ; Calculate x-distance
rsub    10 r2
rcpr    r2 r0
rmul    r3 r4
radd     r0 r2
rcpr    17 r0

    ; Calculate y-distance
rcpf    r2 1.0
rsub    11 r2
rcpr    r2 r0
rmul    r3 r4
radd    r0 r2
rcpr    18 r0

rcpf    r3 0.5
rmul    17 17
rcpr    r1 r0
rmul    18 18
rcpr    r2 r0
rsub    r3 r1
rsub    r0 r2

jumpnn .ifT2L2
jump .elseT2L2
.ifT2L2
    ; No contribution
    rcpf    16 0.0
    jump .finT2L2
.elseT2L2
    ; We are good to contribute. Let's calculate how much:
    rmul    r0 r0
    rmul    r0 r0    ; Lots of squaring...
    rcpr    r4 r0    ; Store this for now in r4

    ; Now we have to read our gradients, let's get the proper location
    rcpi    r0 16    ; Buffer of f64 and 2 values per "section" so compensate for data size
    rmul    r0 16    ; Multiply by our hash offset
    rcpr    r1 r0    ; Store our location in r1 (we have to re-use it shortly)
    rpgf    reg_grad r0
    rcpr    r2 r0    ; Copy our x-val into r2

    rcpi    r0 8
    radd    r1 r0    ; Shift to our next location
    rpgf    reg_grad r0
    rcpr    r3 r0    ; Copy our y-val into r3

    ; Time for a dot product!
    rmul    r2 17    ; x-coord
    rcpr    r1 r0    ; Store in reg1

    rmul    r3 18    ; y-coord
    radd    r1 r0    ; Add to x-coord

    rmul    r0 r4    ; Multiply by our stored value from the beginning
    rcpr    16 r0    ; And store the result, that's one corner down!
.finT2L2

; -- SCALE FINAL RESULT -- ;
; Now we can add up and scale the result between [-1..1]
    ; NOTES:
    ; [float]    reg10 = final result

rcpf    r1 70.0
radd    14 15
radd    r0 16
rmul    r0 r1
rcpr    10 r0

; Store the result in our final buffer:
    ; Calculate which value in the buffer to write to:
sinr    io_width r1
rmul    r1 reg_ay
radd    r0 reg_ax

rcpi    r1 8        ; Values in buffer are floats, so we have to scale by 8 bytes
rmul    r1 r0
rcpr    r1 r0        ; Our byte location is stored in reg1
rpsf    reg_output r1 10    ; Write our final result to the buffer!

; -- DETERMINE LOOP -- ;
; We need to increment our loop bounds and determine if
; we are done with our total calculation.
rcpi    r1 1
radd    reg_ax r1    ; Add 1 to our x array coord
rcpr    reg_ax r0
sinr    io_width r2    ; Grab the width of our chunk

rsub    r2 reg_ax
jumpp .ifINCY
jump .elseINCY
.ifINCY
    ; We reached our bounds, so let's loop it around
    rcpi    reg_ax 0
    rcpi    r1 1
    radd    reg_ay r1
    rcpr    reg_ay r0

    ; Handle our actual simplex coordinate adjustment:
    sinr    io_scalar r1
    radd    reg_y r1
    rcpr    reg_y r0
    sinr    io_xstart reg_x

    ; Since we know we incremented y, let's check if
    ; we are completely done with our loop:
    sinr    io_height r1
    rsub    reg_ay r1
    jumpp .FINAL        ; Jump to the end if we are ready
    jump .finINCY        ; If not, just finish the IF loop
.elseINCY
    ; We DIDN'T reach our bounds, so just modify simplex coord
    sinr    io_scalar r1
    radd    reg_x r1
    rcpr    reg_x r0
.finINCY
jump .MAIN    ; We are all set for the next calculation, start over!

; -- OUTPUT FINAL BUFFER -- ;
.FINAL
srout    io_output reg_output    ; Write it IO port
end                ; Terminate code

; -- MODULO FUNCTION -- ;
    ; INPUTS:
    ; [int]        reg0    = return seek byte
    ; [int]        reg1    = value to operate on
    ; [int]        reg2    = modulo value
    ;
    ; MODIFICATIONS:
    ; [int]        reg1    = updated value
    ; [int]        reg3    = misc values
.funMOD
    rcpr    r3 r0    ; Copy our return location into reg3

    ritof    r1    ; Convert to floats so we can get decimal points
    rcpr    r1 r0
    ritof    r2
    rcpr    r2 r0

    rsub    r1 r2    ; Check if our value is larger than the modulo value
    jumpnn .funMOD_finVLM
    .funMOD_ifVLM    ; Just put this here for readability
        rdiv     r1 r2    ; Divide our value
        rfrac     r0    ; Grab the fractional result
        rmul    r0 r2    ; Multiply it by our modulo
        rcpr    r1 r0    ; Store our final result
    .funMOD_finVLM

    rftoi    r1    ; Convert back to an int
    rcpr    r1 r0

    rcpr    r0 r3    ; Copy our byte-value back into r0
    jumpr        ; Jump to our byte-value
.fun_finMOD        ; Again, just here for readability

Might be kinda hard to follow, but there are examples of how / when most of the function types are used.
 
Last edited:

andev

Member
This is exactly the kind of thing that interests me, have you made anything with this yourself? Or can you think of specific (and practical) use cases?
 

Binsk

Member
@andev I have not yet made any projects that utilize this in a practical fashion. I only wrote it about a week ago so I haven't had time other than putting together the simplex example.

As for both of your questions of a use case; there are a couple that come to mind off-hand.

1) This provides enough functionality to make a semi-competent modding language. You could easily come up with a higher-level language that is broken down into this one. This would allow you to provide a language to your users for designing game mods. It is significantly more complex to compile down straight from a high-level language. Instead all you would have to do is compile it down to this one and the rest is taken care of.

2) You could program aspects of your game that you expect to update down the road with this language and provide the compiled versions of code as external files. Then it would be easier to modify your game and add code post-release without having to release the entire package again. There are many ways to provide this kind of functionality, but this language is another option.

3) As stated in the first post, how this language is executed makes it extremely good for pausing in the middle of code execution. You can pause the code after any operation. Because of this, time-consuming tasks (again, like the simplex noise example) can be automatically split up dynamically over multiple steps so as to prevent lag. I use a very rudimentary method with the runner, but there is nothing from stopping you with making a more competent method.

My main reason for making this was firstly just to challenge myself but also for the sake of building a higher-level modding language. This was the middle step before I could make a higher-level language. The other potential benefits just kind of came with it and I threw in the pseudo-async for kicks.
 

Binsk

Member
Another update:
Code:
- Fixed compiler throwing error with leading tabs
- Fixed some compiler errors causing GM memory leak
- Fixed compiler not catching syntax errors that it should have
- Added macros to the language. Must be defined before header.
- Modified how the runner handles strings (uses buffers now)
- Added string BIC example
- Added rslen
- Added rsgbyte
- Added rssbyte
- Added function names to runner errors
I plan on doing more with strings down the road.

I am considering writing a high-level language and runner that would compile down to BIC so as to allow practical post-compile code additions. It wouldn't be to the state of GML in functionality but it would be as easy to use and expandable.

Would anyone be interested in something like this or would I be wasting my time? It would be a big undertaking and I'm not sure my interest would last until completion if no one is interested.
 

andev

Member
I think this is one of those things where you just have to do it to find out.

If you make the higher level language, you can make examples of use. When people see how powerful your product is in a final implementation, they can truly see whether its worth investing the time to get involved.

If it's any help with motivation, I am interested in seeing where this goes!
 

Binsk

Member
Another update.

Code:
- Changed strings to "pointers" (for arrays), some functions act different
- Added functions to read/write s32 and f64 data types to arrays
- Added function to manually allocate array space (in bytes)
- Pointer IOs can now be set with a string OR buffer
- Pointer IOs when set via BIC will ALWAYS be buffers now, not strings
- Started adding preliminary background code for external runner support
- Added system error category
- Added script bic_clear_errors
- Added compiler / runner / bic versions to headers for down the road
- Fixed GM-side data bug with rftoi and ritof
- Fixed issues that broke the YYC, things run correctly now
- Implemented "auto" mode for the runner to handle task timing by itself
I'm starting work on an external multithreaded runner in C++. We'll see where I go with it. If I end up finishing it then we'll have the ability to run our own code on separate cores/threads without needing specially-designed DLLs (just the one for the runner).

Still will be interpreted code, but hey, it's something. I'm putting the high-level code idea on the backburner for now. I'd like to get an external runner working before digging into that.
 

Binsk

Member
Right, so after spending a good three days finding a single bug, I am just happy to have found it and am posting this darned update without further testing.

Things compile and they work, give it a whirl. Change log:
Code:
- Added rsqrt function
- bic_struct_get_io now returns an array of arrays (now provides IO values and types)
- excall scripts must return an array now where pos 0 = value, pos 1 = type
- Fixed excall bug with buffer returns
- Added new class of error (runner), mostly for external runner
- Fixed seek not being reset after calling "end"
- Fixed bug requiring reg0 to be an int when calling "jump"
- Fixed code buffer bug caused by using "grow" buffer instead of "fixed"
- Added "dumplog" command for debugging 
- Added "trace" command for debugging (external runner only)
- Fixed compiler bug with non-existent macros
- Fixed rounding error when converting from float to int
- Added \" for in-line strings
There are some big changes that aren't in that list, however. Firstly, the external runner is up and working so multi-threading in GameMaker is now a thing (in limited fashion). Secondly, I re-wrote the simplex example; both in BIC and in GameMaker. Both are heavily commented and I demonstrate both the internal and external compiler usage so it should be a bit easier to get your hands dirty.

Let me know what you think and any bugs you come across. Thanks.
 

andev

Member
Now that you've got a solid base, could you make a .gmz with it in action, showcasing its strengths?
 

Binsk

Member
I've provided a yyz demonstrating how it works and it compares the internal runner and external with the use of threads. That's what I'm providing.

What more are you looking for in an example?
 

andev

Member
I mean like, an example game where you showcase how it can be used for modding, task splitting, or any other purpose this is intended for. Kinda like when you go on the game maker website, you get a showcase of what can be done with it!
 

Zodaris

Member
To be fair, a lot of other people made the things on the showcase. And since he's one guy making a tool, it may help to have some people try to use and abuse it for him.

I haven't had a chance to download and test this out yet, but it sounds pretty cool and useful! So this could be used to split up tasks that normally would take a lot of processing power? Would it be able to handle, say, pathfinding for a competitive RTS game where all the units in a group got to be able to move to a location while respecting where all the other units around it are? So like could I set it up where it divides up the work to have each core handle 5 units instead of 1 dealing with 20? As far as user end map making goes, I'll have to mess with it to see if it's how I want it to be, or better. How else could this help in such an RTS game?
 

Binsk

Member
@andev, ah i see. I probably won't go out of my way to do that since this is just a hobby. Firstly i just don't have time to whip up several showcase games and two I'm not really trying to advertise this as a product.

Of course i have some game ideas where i could use this so if i follow through with one I'll post it here for people to look at.

If someone else wants to make something I'd love to see it and post it on the OP.

@Zodaris, path finding would be a perfect application, albeit you'd have to get creative with data storage. The only data this code has to work with is what is in the IO or what it creates itself. That's the most limiting aspect since a DLL can only access what you give it. Since you can pass in buffers, though, you are good as long as you can find a way to store your data in a buffer.

Also, remember that passing data BACK to GameMaker has to sync the thread with the main thread. Because of this, there isn't a benefit if the code can execute in one step.
 

Zodaris

Member
Cool. I'll have to take a look at it. Though I still haven't sat down and looked at Buffers yet, so once I get a firm grasp of that then I'll really look into how to best use this for my uses. So don't expect a showcase item from me for awhile, lol. (I'm also working on 2 projects at once so that'll slow me down as well.)
 
Top