diff options
| author | Mike Martin <[email protected]> | 2020-11-26 23:14:52 -0600 |
|---|---|---|
| committer | Amir Rajan <[email protected]> | 2020-12-06 09:49:41 -0600 |
| commit | 01b16c4244de5232a76713bfd196e34328504021 (patch) | |
| tree | cc80c22b17cb890da0fe869d79547b647c069596 /samples | |
| parent | ee69d1ef44889a8e22752c6d466b10765b3461ca (diff) | |
| download | dragonruby-game-toolkit-contrib-01b16c4244de5232a76713bfd196e34328504021.tar.gz dragonruby-game-toolkit-contrib-01b16c4244de5232a76713bfd196e34328504021.zip | |
Adds basic sample app that utilizes Rust extensions
Diffstat (limited to 'samples')
21 files changed, 268 insertions, 0 deletions
diff --git a/samples/13_rust_extensions/01_basics/.gitignore b/samples/13_rust_extensions/01_basics/.gitignore new file mode 100644 index 0000000..bb0ca95 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/.gitignore @@ -0,0 +1,2 @@ +rust-basic-crate/target/ +Cargo.lock diff --git a/samples/13_rust_extensions/01_basics/README.md b/samples/13_rust_extensions/01_basics/README.md new file mode 100644 index 0000000..7ac5ad2 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/README.md @@ -0,0 +1,111 @@ +# Rust Extensions introduction + +This sample app serves as an introduction on how to write Rust extensions for the +DragonRuby Game Toolkit. You'll need a Pro License +which can be purchased at http://dragonruby.org. The sample app is provided in +the Standard license for those that are curious as to what implementing Rust Extensions +looks like. + +This has only been tested on MacOS but should work on any platform that Rust supports. + +## Requirements + +In order to use Rust extensions you need a C compiler. We strongly recommend you +using [Clang](https://clang.llvm.org). +You'll also need the Rust compiler which you can install using [rustup](https://rustup.rs). +Finally you'll need a rust tool [cbindgen](https://github.com/eqrion/cbindgen). + +### macOS + +To get Clang on macOS it is recommended to install [Xcode](https://developer.apple.com/xcode/). +Once you've done that you should be able to use Clang from a terminal: + +``` +> clang --version +Apple clang version 11.0.3 (clang-1103.0.32.29) +Target: x86_64-apple-darwin19.5.0 +Thread model: posix +InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin +``` + +## Hello World + +Let's craft the simplest possible Rust extension: a simple function that +calculates square of an integer. First create your rust crate: + +``` +> cargo new --lib my-rust-crate +``` + +Update the cargo metadata to specify you want a cdylib as the build output + +add the following to `my-rust-crate/Cargo.toml`: +``` +[lib] +crate-type = ["cdylib"] +``` + +Put the following code into `my-rust-crate/src/lib.rs` + +``` +#[no_mangle] +pub extern "C" fn square(x: i64) -> i64 { + x * x +} +``` + +See [The Rust Documentation](https://rust-embedded.github.io/book/interoperability/rust-with-c.html) for more information on how to write Rust functions that can be embedded in a C project. + +You need some glue code to connect this function to the GameToolKit. Good news +are that you don't need to craft this code yourself: it can be generated for +you by `dragonruby-bind`. + +Run the following shell script to generate the glue code and build the rust shared library from Linux/macOS terminal: + +``` +> ./pre.sh +``` + +There currently is a bug that will generate an error in the `dragonruby-bind` step but you can safely ignore it. + +Now, include the following snippet into the very beginning of `mygame/app/main.rb`: + +``` +$gtk.ffi_misc.gtk_dlopen("ext") +include FFI::CExt +puts square(11) +``` + +Now, simply run `dragonruby` (or `dragonruby.exe`) and you should see `121` on +the console. + +Let's do a breakdown of each line! + +1. `$gtk.ffi_misc.gtk_dlopen("ext")` - DragonRuby exposes a special function + called `gtk_dlopen`, you can use it to load a dynamic that holds + the C extension code. It looks for the shared library in + "mygame/native/$PLATFORM/ext.$PLATFORM_DLL_EXTENSION" +2. `include FFI::CExt` - by default, DragonRuby puts all the code available in + the C extension under `FFI::CExt` module. This line serves as a shortcut so + that you don't need to write `FFI::CExt::square` any time you want to call + a function. +3. `puts square(11)` - this line simply prints the value returned from C code. + +Now, you can call the `square` function at any place in the code. Let's see +what's the square value of every pixel on the screen. Here is the full program: + +``` +$gtk.ffi_misc.gtk_dlopen("ext") +include FFI::CExt + +def tick args + args.outputs.labels << [640, 500, "mouse.x = #{args.mouse.x.to_i}", 5, 1] + args.outputs.labels << [640, 460, "square(mouse.x) = #{square(args.mouse.x.to_i)}", 5, 1] + args.outputs.labels << [640, 420, "mouse.y = #{args.mouse.y.to_i}", 5, 1] + args.outputs.labels << [640, 380, "square(mouse.y) = #{square(args.mouse.y.to_i)}", 5, 1] +end +``` + +When you run the game now, you will see something like this: + + diff --git a/samples/13_rust_extensions/01_basics/app/ext.h b/samples/13_rust_extensions/01_basics/app/ext.h new file mode 100644 index 0000000..6f7b750 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/app/ext.h @@ -0,0 +1,6 @@ +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +int64_t square(int64_t x); diff --git a/samples/13_rust_extensions/01_basics/app/main.rb b/samples/13_rust_extensions/01_basics/app/main.rb new file mode 100644 index 0000000..2d61fa3 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/app/main.rb @@ -0,0 +1,10 @@ +$gtk.ffi_misc.gtk_dlopen("ext") +include FFI::CExt + +def tick args + args.outputs.labels << [640, 500, "mouse.x = #{args.mouse.x.to_i}", 5, 1] + args.outputs.labels << [640, 460, "square(mouse.x) = #{square(args.mouse.x.to_i)}", 5, 1] + args.outputs.labels << [640, 420, "mouse.y = #{args.mouse.y.to_i}", 5, 1] + args.outputs.labels << [640, 380, "square(mouse.y) = #{square(args.mouse.y.to_i)}", 5, 1] +end + diff --git a/samples/13_rust_extensions/01_basics/license-for-sample.txt b/samples/13_rust_extensions/01_basics/license-for-sample.txt new file mode 100644 index 0000000..e8425da --- /dev/null +++ b/samples/13_rust_extensions/01_basics/license-for-sample.txt @@ -0,0 +1,9 @@ +Copyright 2020 DragonRuby LLC + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/samples/13_rust_extensions/01_basics/metadata/game_metadata.txt b/samples/13_rust_extensions/01_basics/metadata/game_metadata.txt new file mode 100644 index 0000000..4112539 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/metadata/game_metadata.txt @@ -0,0 +1,5 @@ +devid=dragonruby +devtitle=DragonRuby LLC +gameid=rustbasics +gametitle=Rust Basics +version=1.0 diff --git a/samples/13_rust_extensions/01_basics/native/android-amd64/ext.so b/samples/13_rust_extensions/01_basics/native/android-amd64/ext.so Binary files differnew file mode 100755 index 0000000..f224773 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/native/android-amd64/ext.so diff --git a/samples/13_rust_extensions/01_basics/native/android-arm32/ext.so b/samples/13_rust_extensions/01_basics/native/android-arm32/ext.so Binary files differnew file mode 100755 index 0000000..ea873ac --- /dev/null +++ b/samples/13_rust_extensions/01_basics/native/android-arm32/ext.so diff --git a/samples/13_rust_extensions/01_basics/native/android-arm64/ext.so b/samples/13_rust_extensions/01_basics/native/android-arm64/ext.so Binary files differnew file mode 100755 index 0000000..5692a2a --- /dev/null +++ b/samples/13_rust_extensions/01_basics/native/android-arm64/ext.so diff --git a/samples/13_rust_extensions/01_basics/native/android-x86/ext.so b/samples/13_rust_extensions/01_basics/native/android-x86/ext.so Binary files differnew file mode 100755 index 0000000..2724cf8 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/native/android-x86/ext.so diff --git a/samples/13_rust_extensions/01_basics/native/ext-bindings.c b/samples/13_rust_extensions/01_basics/native/ext-bindings.c new file mode 100644 index 0000000..9d8c085 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/native/ext-bindings.c @@ -0,0 +1,76 @@ +#include <mruby.h> +#include <string.h> +#include <assert.h> +#include <mruby/string.h> +#include <mruby/data.h> +#include <dragonruby.h> +#include "app/ext.h" + +// MRuby `typedef`s mrb_int in the mruby/value.h +// Then `#define`s mrb_int in mruby.h +// We need to undo the macro and avoid it's usage +// FIXME: I'm surely doing something wrong +#ifdef mrb_int +#undef mrb_int +#endif + +void *(*drb_symbol_lookup)(const char *sym) = NULL; + +static void (*drb_free_foreign_object_f)(mrb_state *, void *); +static struct RClass *(*mrb_module_get_f)(mrb_state *, const char *); +static mrb_int (*mrb_get_args_f)(mrb_state *, mrb_args_format, ...); +static struct RClass *(*mrb_module_get_under_f)(mrb_state *, struct RClass *, const char *); +static struct RClass *(*mrb_class_get_under_f)(mrb_state *, struct RClass *, const char *); +static struct RClass *(*mrb_define_module_under_f)(mrb_state *, struct RClass *, const char *); +static void (*mrb_define_module_function_f)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec); +static struct RClass *(*mrb_define_class_under_f)(mrb_state *, struct RClass *, const char *, struct RClass *); +static void (*mrb_define_method_f)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec); +static void (*mrb_define_class_method_f)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec); +static struct RData *(*mrb_data_object_alloc_f)(mrb_state *, struct RClass *, void *, const mrb_data_type *); +static mrb_value (*mrb_str_new_cstr_f)(mrb_state *, const char *); +static void (*mrb_raise_f)(mrb_state *, struct RClass *, const char *); +static struct RClass *(*mrb_exc_get_f)(mrb_state *, const char *); +static void drb_free_foreign_object_indirect(mrb_state *state, void *pointer) { + drb_free_foreign_object_f(state, pointer); +} +static int drb_ffi__ZTSi_FromRuby(mrb_state *state, mrb_value self) { + return mrb_fixnum(self); +} +static mrb_value drb_ffi__ZTSi_ToRuby(mrb_state *state, int value) { + return mrb_fixnum_value(value); +} +static mrb_value drb_ffi_square_Binding(mrb_state *state, mrb_value value) { + mrb_value *args = 0; + mrb_int argc = 0; + mrb_get_args_f(state, "*", &args, &argc); + int x_0 = drb_ffi__ZTSi_FromRuby(state, args[0]); + int ret_val = square(x_0); + return drb_ffi__ZTSi_ToRuby(state, ret_val); +} +static int drb_ffi_init_indirect_functions(void *(*lookup)(const char *)); +DRB_FFI_EXPORT +void drb_register_c_extensions(void *(*lookup)(const char *), mrb_state *state, struct RClass *FFI) { + if (drb_ffi_init_indirect_functions(lookup)) + return; + struct RClass *module = mrb_define_module_under_f(state, FFI, "CExt"); + struct RClass *object_class = state->object_class; + mrb_define_module_function_f(state, module, "square", drb_ffi_square_Binding, MRB_ARGS_REQ(1)); +} +static int drb_ffi_init_indirect_functions(void *(*lookup)(const char *fnname)) { + drb_symbol_lookup = lookup; + if (!(drb_free_foreign_object_f = (void (*)(mrb_state *, void *)) lookup("drb_free_foreign_object"))) return -1; + if (!(mrb_class_get_under_f = (struct RClass *(*)(mrb_state *, struct RClass *, const char *)) lookup("mrb_class_get_under"))) return -1; + if (!(mrb_data_object_alloc_f = (struct RData *(*)(mrb_state *, struct RClass *, void *, const mrb_data_type *)) lookup("mrb_data_object_alloc"))) return -1; + if (!(mrb_define_class_method_f = (void (*)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec)) lookup("mrb_define_class_method"))) return -1; + if (!(mrb_define_class_under_f = (struct RClass *(*)(mrb_state *, struct RClass *, const char *, struct RClass *)) lookup("mrb_define_class_under"))) return -1; + if (!(mrb_define_method_f = (void (*)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec)) lookup("mrb_define_method"))) return -1; + if (!(mrb_define_module_function_f = (void (*)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec)) lookup("mrb_define_module_function"))) return -1; + if (!(mrb_define_module_under_f = (struct RClass *(*)(mrb_state *, struct RClass *, const char *)) lookup("mrb_define_module_under"))) return -1; + if (!(mrb_exc_get_f = (struct RClass *(*)(mrb_state *, const char *)) lookup("mrb_exc_get"))) return -1; + if (!(mrb_get_args_f = (mrb_int (*)(mrb_state *, mrb_args_format, ...)) lookup("mrb_get_args"))) return -1; + if (!(mrb_module_get_f = (struct RClass *(*)(mrb_state *, const char *)) lookup("mrb_module_get"))) return -1; + if (!(mrb_module_get_under_f = (struct RClass *(*)(mrb_state *, struct RClass *, const char *)) lookup("mrb_module_get_under"))) return -1; + if (!(mrb_raise_f = (void (*)(mrb_state *, struct RClass *, const char *)) lookup("mrb_raise"))) return -1; + if (!(mrb_str_new_cstr_f = (mrb_value (*)(mrb_state *, const char *)) lookup("mrb_str_new_cstr"))) return -1; + return 0; +} diff --git a/samples/13_rust_extensions/01_basics/native/linux-amd64/ext.so b/samples/13_rust_extensions/01_basics/native/linux-amd64/ext.so Binary files differnew file mode 100755 index 0000000..eab7ab4 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/native/linux-amd64/ext.so diff --git a/samples/13_rust_extensions/01_basics/native/linux-raspberrypi/ext.so b/samples/13_rust_extensions/01_basics/native/linux-raspberrypi/ext.so Binary files differnew file mode 100755 index 0000000..06622a2 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/native/linux-raspberrypi/ext.so diff --git a/samples/13_rust_extensions/01_basics/native/macos/ext.dylib b/samples/13_rust_extensions/01_basics/native/macos/ext.dylib Binary files differnew file mode 100755 index 0000000..018b9fc --- /dev/null +++ b/samples/13_rust_extensions/01_basics/native/macos/ext.dylib diff --git a/samples/13_rust_extensions/01_basics/native/windows-amd64/ext.dll b/samples/13_rust_extensions/01_basics/native/windows-amd64/ext.dll Binary files differnew file mode 100755 index 0000000..803db2e --- /dev/null +++ b/samples/13_rust_extensions/01_basics/native/windows-amd64/ext.dll diff --git a/samples/13_rust_extensions/01_basics/pre.sh b/samples/13_rust_extensions/01_basics/pre.sh new file mode 100755 index 0000000..daf23f9 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/pre.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +OSTYPE=`uname -s` +if [ "x$OSTYPE" = "xDarwin" ]; then + PLATFORM=macos + DLLEXT=dylib +else + PLATFORM=linux-amd64 + DLLEXT=so +fi + +DRB_ROOT=../../.. +mkdir -p native/$PLATFORM + +pushd rust-basic-crate +cargo build --release +cbindgen --config cbindgen.toml --crate rust-basic-crate --output ../app/ext.h +popd + +$DRB_ROOT/dragonruby-bind --output=native/ext-bindings.c app/ext.h +echo "\nIgnore the above error about #include\n" +clang \ + -isystem $DRB_ROOT/include -I. \ + -fPIC -shared native/ext-bindings.c rust-basic-crate/target/release/librust_basic_crate.$DLLEXT -o native/$PLATFORM/ext.$DLLEXT + diff --git a/samples/13_rust_extensions/01_basics/run b/samples/13_rust_extensions/01_basics/run new file mode 100755 index 0000000..2172c7e --- /dev/null +++ b/samples/13_rust_extensions/01_basics/run @@ -0,0 +1,7 @@ +#!/bin/sh +cd "`dirname "$0"`" +./pre.sh +cd .. +cd .. +cd .. +exec ./dragonruby samples/13_rust_extensions/01_basics diff --git a/samples/13_rust_extensions/01_basics/rust-basic-crate/Cargo.toml b/samples/13_rust_extensions/01_basics/rust-basic-crate/Cargo.toml new file mode 100644 index 0000000..34c01f1 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/rust-basic-crate/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rust-basic-crate" +version = "0.1.0" +authors = ["Mike Martin <[email protected]>"] +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/samples/13_rust_extensions/01_basics/rust-basic-crate/cbindgen.toml b/samples/13_rust_extensions/01_basics/rust-basic-crate/cbindgen.toml new file mode 100644 index 0000000..08094f2 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/rust-basic-crate/cbindgen.toml @@ -0,0 +1 @@ +language = "C" diff --git a/samples/13_rust_extensions/01_basics/rust-basic-crate/src/lib.rs b/samples/13_rust_extensions/01_basics/rust-basic-crate/src/lib.rs new file mode 100644 index 0000000..042d9cd --- /dev/null +++ b/samples/13_rust_extensions/01_basics/rust-basic-crate/src/lib.rs @@ -0,0 +1,4 @@ +#[no_mangle] +pub extern "C" fn square(x: i64) -> i64 { + x * x +} diff --git a/samples/13_rust_extensions/01_basics/rust-basic-demo.png b/samples/13_rust_extensions/01_basics/rust-basic-demo.png Binary files differnew file mode 100644 index 0000000..715c0d9 --- /dev/null +++ b/samples/13_rust_extensions/01_basics/rust-basic-demo.png |
