WARNING:
This post is not suitable for newbies, perhaps also not applied to the game that doesn't have a performance issue. It's suggested to know the following knowledge to fully understand how it works:
RMVA Scripting experience(most important one, required)
Basic C/C++ knowledge
Familiar with Ruby and C/C++
PE(portable executable) and DLL(Dynamic Link Library)
Don't panic if you're not familiar with C/C++, I'll try my best to explain anyway.
A very basic thing that you should know is the datatype of all ruby object in C level is 'VALUE', a pointer to the object except some very basic object, like Fixnum, and you can see this part in ruby.c:
Code:
in this line, SIGNED_VALUE generally is LONG(aka int) and FIXNUM_FLAG is 1, so you can think Fixnum is encoded by (n * 2 + 1) instead of the pointer. However, the VALUE of String and Float are both pointers. And you can get the object's address by:
Code:
obj.object_id << 1
复制代码
(as known as multiply the object_id by 2)
we'll need that to pass the object from Game.exe to our dll later
------------------------I'm a random split line------------------------
Currently, I'm making a unique engine for my game, and as larger as it gets, the performance issue comes up...(duh)
Just like Python, one of the popular way to resolve this problem in interpreter language is mixing it with C-level code to speed up and improve the calculation significantly.
Before getting started, you will need:
- Ruby 1.9.2-p0 source code (download here), this is the ruby version that rgss using
- A C/C++ IDE, Visual Studio is a common option, but I also using Code::Blocks, VS is using for debugging DLL
- (optional) A debugger (I use VS), because the game will crash silently if something wrong with the dll
Once got the software running and prepared the DLL project, the next step is telling the compiler to know what's Ruby object, notice that we don't have to compile the ruby source code itself. Since RubyVM is running inside RGSS301.dll so we only need to declare necessary functions and datatypes and call RGSS301.dll while building up the dynamic link library. But if you're as silly as I am...managed to include the whole ruby to the dll and using the ruby function... Congratulations! You'll get ruby_vm_current_ptr is nullptr and the game crashes! (I f*cking even compile the whole ruby natively and it took me ages to build successfully...)
If you don't want to copy, edit and post the ruby source code, you can go download header files here and put them in the project folder, then include "ruby.h", this way you should have no trouble using VALUE in your C/CPP file. So the .hpp or .h file should like:
Code:
#include <windows.h>
#include "ruby.h"
复制代码
Next, we're heading to the example, and what I gonna show here is how to improve the projectile collide detection, here're also some extra tips you should know while writing c code:
- Access VALUE as less as possible, the dll is slower than getting VALUE in RGSS
- Don't wrap the functions that frequently calling other methods into dll, the reason is above
- From 2 above, things needed to write into C-level are calculation-heavy functions
- Since Float are pointers, so using Fixnum if you can, and convert them to int and store in your C code
- avoid using std::vector, using new int [] if need to deal with fixed-length array
since the time complexity of simplest collide detection is O(n^2), only need O(n) time to access VALUE data, also not affect gameplay much if the projectile colliding effect is delayed by 1 frame so it's good to put that into C.
Before jump into the demo and explain the code, here's the result of my little experiment, I also used Theo's Anti-Lag to make the result better due to the fact that tons of projectiles also means tons of active sprites(unfortunately RMVA is not good at graphics processing), and the measurement is making a constantly updated event run from A point to B point 10 times, since the lag will slow the entire game down, so the one take less time is the better result.
Experiment Info:
> Using collision box as collide detection
> Distance of A to B: 10 tiles
> Measurement Event: x4 Faster Speed, Highest Frequency, through
> 5 projectiles are generated per frame update
> Maximum visible sprites number is 100/1000000000 (over will become invisible projectile lol)
> Total events: 277
> Total battler event: 65
※The result will vary on different hardware, tested using I5-6200U/4GB laptop
Spoiler: Result (text only)
No projectile:
Average 1.0149 seconds in 10 runs
100 sprite limit, no dll nor anti-lag script:
Average 5.4282 seconds in 10 runs; lim -> 5.98 seconds with around 620 projectiles
100 sprite limit, with dll only:
Average 3.1801 seconds in 10 runs; lim -> 3.38 seconds with around 620 projectiles
100 sprite limit, with ATL only:
Average 3.3929 seconds in 10 runs; lim -> 3.92 seconds with around 676 projectiles
100 sprite limit, with both dll and ATL:
Average 1.3161 seconds in 10 runs; lim -> 1.39 seconds with around 676 projectiles
1,000,000,000 sprite limit, with dll only:
Average 4.3676 seconds in 10 runs; lim -> 5.11 seconds with around 650 projectiles
1,000,000,000 sprite limit, with ATL only:
Average 4.0854 seconds in 10 runs; lim -> 4.92 seconds with around 693 projectiles
1,000,000,000 sprite limit, with both dll and ATL:]
Average 2.3111 seconds in 10 runs; lim -> 3.09 seconds with around 693 projectiles
Spoiler: Result (with images)
No projectile:
No anti-lag, native ruby collision detection, 100 sprite limit
with anti-lag, native ruby code, 100 sprite limit
1000000000 limit:
no anti-lag, C-level collide detection code, 100 sprite limit