diff options
| author | Amir Rajan <[email protected]> | 2020-10-13 00:45:16 -0500 |
|---|---|---|
| committer | Amir Rajan <[email protected]> | 2020-10-13 00:48:54 -0500 |
| commit | 05cbef7fb8224332795e5685be499d81d20e7d93 (patch) | |
| tree | 13ec5f1755c2f45618741f2f016ed8729dbedd41 /samples | |
| parent | abad948c1154d88d79b9f891e3b7315540e0b0a3 (diff) | |
| download | dragonruby-game-toolkit-contrib-05cbef7fb8224332795e5685be499d81d20e7d93.tar.gz dragonruby-game-toolkit-contrib-05cbef7fb8224332795e5685be499d81d20e7d93.zip | |
Synced with 1.26.
Diffstat (limited to 'samples')
33 files changed, 1814 insertions, 13 deletions
diff --git a/samples/01_rendering_basics/06_audio_mixer/app/main.rb b/samples/01_rendering_basics/06_audio_mixer/app/main.rb new file mode 100644 index 0000000..a09c7f1 --- /dev/null +++ b/samples/01_rendering_basics/06_audio_mixer/app/main.rb @@ -0,0 +1,158 @@ +$gtk.reset + +$boxsize = 30 + +def render_sources args + mouse_in_panel = (args.state.selected != 0) && args.inputs.mouse.position.inside_rect?([900, 450, 340, 250]) + mouse_new_down = (args.state.mouse_held == 1) + + if (mouse_new_down && !mouse_in_panel) + args.state.selected = 0 # will reset below if we hit something. + end + + args.audio.keys.each { |k| + s = args.audio[k] + + if (mouse_new_down) && !mouse_in_panel && args.inputs.mouse.position.inside_rect?([s[:screenx], s[:screeny], $boxsize, $boxsize]) + args.state.selected = k + args.state.dragging_source = true + end + + isselected = (k == args.state.selected) + + if isselected && args.state.dragging_source + # you can hang anything on the audio hashes you want, so we store the + # actual screen position so it doesn't scale weirdly vs your mouse. + s[:screenx] = args.inputs.mouse.x - ($boxsize / 2) + s[:screeny] = args.inputs.mouse.y - ($boxsize / 2) + + s[:screeny] = 50 if s[:screeny] < 50 + s[:screeny] = (719 - $boxsize) if s[:screeny] > (719 - $boxsize) + s[:screenx] = 0 if s[:screenx] < 0 + s[:screenx] = (1279 - $boxsize) if s[:screenx] > (1279 - $boxsize) + + s[:x] = ((s[:screenx] / 1279.0) * 2.0) - 1.0 # scale to -1.0 - 1.0 range + s[:y] = ((s[:screeny] / 719.0) * 2.0) - 1.0 # scale to -1.0 - 1.0 range + end + + color = isselected ? [ 0, 255, 0, 255 ] : [ 0, 0, 255, 255 ] + args.outputs.primitives << [s[:screenx], s[:screeny], $boxsize, $boxsize, *color].solid + } +end + +def render_panel args + s = args.audio[args.state.selected] + return if s.nil? + mouse_down = (args.state.mouse_held > 0) + + args.outputs.primitives << [900, 450, 340, 250, 127, 127, 200, 255].solid + args.outputs.primitives << [1075, 690, "Source ##{args.state.selected}", 3, 1, 255, 255, 255].label + args.outputs.primitives << [910, 660, 1230, 660, 255, 255, 255].line + args.outputs.primitives << [910, 650, "screen: (#{s[:screenx].to_i}, #{s[:screeny].to_i})", 0, 0, 255, 255, 255].label + args.outputs.primitives << [910, 625, "position: (#{s[:x].round(5).to_s[0..6]}, #{s[:y].round(5).to_s[0..6]})", 0, 0, 255, 255, 255].label + + slider = [1022, 586, 200, 7] + if mouse_down && args.inputs.mouse.position.inside_rect?(slider) + s[:pitch] = ((args.inputs.mouse.x - slider[0]).to_f / (slider[2]-1.0)) * 2.0 + end + slidercolor = (s[:pitch] / 2.0) * 255 + args.outputs.primitives << [*slider, slidercolor, slidercolor, slidercolor, 255].solid + args.outputs.primitives << [910, 600, "pitch: #{s[:pitch].round(3).to_s[0..2]}", 0, 0, 255, 255, 255].label + + slider = [1022, 561, 200, 7] + if mouse_down && args.inputs.mouse.position.inside_rect?(slider) + s[:gain] = (args.inputs.mouse.x - slider[0]).to_f / (slider[2]-1.0) + end + slidercolor = s[:gain] * 255 + args.outputs.primitives << [*slider, slidercolor, slidercolor, slidercolor, 255].solid + args.outputs.primitives << [910, 575, "gain: #{s[:gain].round(3).to_s[0..2]}", 0, 0, 255, 255, 255].label + + checkbox = [1022, 533, 10, 12] + if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(checkbox) + s[:looping] = !s[:looping] + end + checkboxcolor = s[:looping] ? 255 : 0 + args.outputs.primitives << [*checkbox, checkboxcolor, checkboxcolor, checkboxcolor, 255].solid + args.outputs.primitives << [910, 550, "looping:", 0, 0, 255, 255, 255].label + + checkbox = [1022, 508, 10, 12] + if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(checkbox) + s[:paused] = !s[:paused] + end + checkboxcolor = s[:paused] ? 255 : 0 + args.outputs.primitives << [*checkbox, checkboxcolor, checkboxcolor, checkboxcolor, 255].solid + args.outputs.primitives << [910, 525, "paused:", 0, 0, 255, 255, 255].label + + button = [910, 460, 320, 20] + if (args.state.mouse_held == 1) && args.inputs.mouse.position.inside_rect?(button) + args.audio.delete(args.state.selected) + args.state.selected = 0 + end + args.outputs.primitives << [*button, 255, 0, 0, 255].solid + args.outputs.primitives << [button[0] + (button[2] / 2), button[1]+20, "DELETE SOURCE", 0, 1, 255, 255, 0].label +end + +def spawn_new_sound args, num + # Spawn randomly in an area that won't be covered by UI. + screenx = (rand * 600.0) + 200.0 + screeny = (rand * 400.0) + 100.0 + + args.state.next_sound_index += 1 + + # you can hang anything on the audio hashes you want, so we store the + # actual screen position in here for convenience. + args.audio[args.state.next_sound_index] = { + filename: "sounds/#{num}.#{(num == 6) ? 'ogg' : 'wav'}", + screenx: screenx, + screeny: screeny, + x: ((screenx / 1279.0) * 2.0) - 1.0, # scale to -1.0 - 1.0 range + y: ((screeny / 719.0) * 2.0) - 1.0, # scale to -1.0 - 1.0 range + z: 0.0, + gain: 1.0, + pitch: 1.0, + looping: true, + paused: false + } + + args.state.selected = args.state.next_sound_index +end + +def render_launcher args + total = 6 + x = (1280 - (total * $boxsize * 3)) / 2 + y = 10 + args.outputs.primitives << [0, 0, 1280, ((y*2) + $boxsize), 127, 127, 127, 255].solid + for i in 1..total + args.outputs.primitives << [x, y, $boxsize, $boxsize, 255, 255, 255, 255].solid + args.outputs.primitives << [x+8, y+28, i.to_s, 3, 0, 0, 0, 255, 255].label + if args.inputs.mouse.click && args.inputs.mouse.click.point.inside_rect?([x, y, $boxsize, $boxsize]) + spawn_new_sound args, i + end + x = x + ($boxsize * 3) + end +end + +def render_ui args + render_launcher args + render_panel args +end + +def tick args + args.state.mouse_held ||= 0 + args.state.dragging_source ||= false + args.state.selected ||= 0 + args.state.next_sound_index ||= 0 + + if args.inputs.mouse.up + args.state.mouse_held = 0 + args.state.dragging_source = false + elsif args.inputs.mouse.down || (args.state.mouse_held > 0) + args.state.mouse_held += 1 + else + end + + args.outputs.background_color = [ 0, 0, 0, 255 ] + render_sources args + render_ui args +end + diff --git a/samples/01_rendering_basics/06_audio_mixer/license-for-audio.txt b/samples/01_rendering_basics/06_audio_mixer/license-for-audio.txt new file mode 100644 index 0000000..f05c25c --- /dev/null +++ b/samples/01_rendering_basics/06_audio_mixer/license-for-audio.txt @@ -0,0 +1,48 @@ +The audio files in this sample are used under various Creative Common licenses: + + +sounds/1.wav: + + Party Pack, Horn Coil 01, Long, 01.wav by InspectorJ (www.jshaw.co.uk) of Freesound.org + (We converted this to mono output.) + Original: https://freesound.org/s/484267/ + License: https://creativecommons.org/licenses/by/3.0/ + + +sounds/2.wav: + + SPLASH.wav by petenice + Original: https://freesound.org/s/9508/ + License: https://creativecommons.org/publicdomain/zero/1.0/ + + +sounds/3.wav: + + buzz roll.wav by bigjoedrummer + (We converted this to mono output.) + Original: https://freesound.org/s/77305/ + License: https://creativecommons.org/publicdomain/zero/1.0/ + + +sounds/4.wav: + + sword04.wav by Erdie + Original: https://freesound.org/s/27858/ + License: https://creativecommons.org/licenses/by/3.0/ + + +sounds/5.wav: + + Game Over Arcade.wav by myfox14 + (We converted this to mono output.) + Original: https://freesound.org/s/382310/ + License: https://creativecommons.org/publicdomain/zero/1.0/ + + +sounds/6.ogg: + + Arcade Music Loop.wav by joshuaempyre ( https://www.empyreanma.com/welcome ) + (We converted this to Ogg Vorbis format, and mono output.) + Original: https://freesound.org/s/251461/ + License: https://creativecommons.org/licenses/by/3.0/ + diff --git a/samples/01_rendering_basics/06_audio_mixer/license-for-sample.txt b/samples/01_rendering_basics/06_audio_mixer/license-for-sample.txt new file mode 100644 index 0000000..100dcec --- /dev/null +++ b/samples/01_rendering_basics/06_audio_mixer/license-for-sample.txt @@ -0,0 +1,9 @@ +Copyright 2019 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/01_rendering_basics/06_audio_mixer/sounds/1.wav b/samples/01_rendering_basics/06_audio_mixer/sounds/1.wav Binary files differnew file mode 100644 index 0000000..8aa1be3 --- /dev/null +++ b/samples/01_rendering_basics/06_audio_mixer/sounds/1.wav diff --git a/samples/01_rendering_basics/06_audio_mixer/sounds/2.wav b/samples/01_rendering_basics/06_audio_mixer/sounds/2.wav Binary files differnew file mode 100644 index 0000000..52a09c2 --- /dev/null +++ b/samples/01_rendering_basics/06_audio_mixer/sounds/2.wav diff --git a/samples/01_rendering_basics/06_audio_mixer/sounds/3.wav b/samples/01_rendering_basics/06_audio_mixer/sounds/3.wav Binary files differnew file mode 100644 index 0000000..c7bde27 --- /dev/null +++ b/samples/01_rendering_basics/06_audio_mixer/sounds/3.wav diff --git a/samples/01_rendering_basics/06_audio_mixer/sounds/4.wav b/samples/01_rendering_basics/06_audio_mixer/sounds/4.wav Binary files differnew file mode 100644 index 0000000..193cf54 --- /dev/null +++ b/samples/01_rendering_basics/06_audio_mixer/sounds/4.wav diff --git a/samples/01_rendering_basics/06_audio_mixer/sounds/5.wav b/samples/01_rendering_basics/06_audio_mixer/sounds/5.wav Binary files differnew file mode 100644 index 0000000..112f3cc --- /dev/null +++ b/samples/01_rendering_basics/06_audio_mixer/sounds/5.wav diff --git a/samples/01_rendering_basics/06_audio_mixer/sounds/6.ogg b/samples/01_rendering_basics/06_audio_mixer/sounds/6.ogg Binary files differnew file mode 100644 index 0000000..a952191 --- /dev/null +++ b/samples/01_rendering_basics/06_audio_mixer/sounds/6.ogg diff --git a/samples/07_advanced_rendering/02_render_targets_with_tile_manipulation/app/main.rb b/samples/07_advanced_rendering/02_render_targets_with_tile_manipulation/app/main.rb new file mode 100644 index 0000000..2caec43 --- /dev/null +++ b/samples/07_advanced_rendering/02_render_targets_with_tile_manipulation/app/main.rb @@ -0,0 +1,95 @@ +# This sample is meant to show you how to do that dripping transition thing +# at the start of the original Doom. Most of this file is here to animate +# a scene to wipe away; the actual wipe effect is in the last 20 lines or +# so. + +$gtk.reset # reset all game state if reloaded. + +def circle_of_blocks pass, xoffset, yoffset, angleoffset, blocksize, distance + numblocks = 10 + + for i in 1..numblocks do + angle = ((360 / numblocks) * i) + angleoffset + radians = angle * (Math::PI / 180) + x = (xoffset + (distance * Math.cos(radians))).round + y = (yoffset + (distance * Math.sin(radians))).round + pass.solids << [ x, y, blocksize, blocksize, 255, 255, 0 ] + end +end + +def draw_scene args, pass + pass.solids << [0, 360, 1280, 360, 0, 0, 200] + pass.solids << [0, 0, 1280, 360, 0, 127, 0] + + blocksize = 100 + angleoffset = args.state.tick_count * 2.5 + centerx = (1280 - blocksize) / 2 + centery = (720 - blocksize) / 2 + + circle_of_blocks pass, centerx, centery, angleoffset, blocksize * 2, 500 + circle_of_blocks pass, centerx, centery, angleoffset, blocksize, 325 + circle_of_blocks pass, centerx, centery, angleoffset, blocksize / 2, 200 + circle_of_blocks pass, centerx, centery, angleoffset, blocksize / 4, 100 +end + +def tick args + segments = 160 + + # On the first tick, initialize some stuff. + if !args.state.yoffsets + args.state.baseyoff = 0 + args.state.yoffsets = [] + for i in 0..segments do + args.state.yoffsets << rand * 100 + end + end + + # Just draw some random stuff for a few seconds. + args.state.static_debounce ||= 60 * 2.5 + if args.state.static_debounce > 0 + last_frame = args.state.static_debounce == 1 + target = last_frame ? args.render_target(:last_frame) : args.outputs + draw_scene args, target + args.state.static_debounce -= 1 + return unless last_frame + end + + # build up the wipe... + + # this is the thing we're wiping to. + args.outputs.sprites << [ 0, 0, 1280, 720, 'dragonruby.png' ] + + return if (args.state.baseyoff > (1280 + 100)) # stop when done sliding + + segmentw = 1280 / segments + + x = 0 + for i in 0..segments do + yoffset = 0 + if args.state.yoffsets[i] < args.state.baseyoff + yoffset = args.state.baseyoff - args.state.yoffsets[i] + end + + # (720 - yoffset) flips the coordinate system, (- 720) adjusts for the height of the segment. + args.outputs.sprites << [ x, (720 - yoffset) - 720, segmentw, 720, 'last_frame', 0, 255, 255, 255, 255, x, 0, segmentw, 720 ] + x += segmentw + end + + args.state.baseyoff += 4 + + tick_instructions args, "Sample app shows an advanced usage of render_target." +end + +def tick_instructions args, text, y = 715 + return if args.state.key_event_occurred + if args.inputs.mouse.click || + args.inputs.keyboard.directional_vector || + args.inputs.keyboard.key_down.enter || + args.inputs.keyboard.key_down.escape + args.state.key_event_occurred = true + end + + args.outputs.debug << [0, y - 50, 1280, 60].solid + args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label + args.outputs.debug << [640, y - 25, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label +end diff --git a/samples/07_advanced_rendering/02_render_targets_with_tile_manipulation/license-for-sample.txt b/samples/07_advanced_rendering/02_render_targets_with_tile_manipulation/license-for-sample.txt new file mode 100644 index 0000000..100dcec --- /dev/null +++ b/samples/07_advanced_rendering/02_render_targets_with_tile_manipulation/license-for-sample.txt @@ -0,0 +1,9 @@ +Copyright 2019 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/09_performance/01_sprites_as_hash/app/main.rb b/samples/09_performance/01_sprites_as_hash/app/main.rb index 2bb3f46..6bbb295 100644 --- a/samples/09_performance/01_sprites_as_hash/app/main.rb +++ b/samples/09_performance/01_sprites_as_hash/app/main.rb @@ -1,3 +1,4 @@ + # Sprites represented as Hashes using the queue ~args.outputs.sprites~ # code up, but are the "slowest" to render. # The reason for this is the access of the key in the Hash and also diff --git a/samples/10_advanced_debugging/03_unit_tests/serialize_deserialize_tests.rb b/samples/10_advanced_debugging/03_unit_tests/serialize_deserialize_tests.rb index 22fa862..e67b1ee 100644 --- a/samples/10_advanced_debugging/03_unit_tests/serialize_deserialize_tests.rb +++ b/samples/10_advanced_debugging/03_unit_tests/serialize_deserialize_tests.rb @@ -2,12 +2,12 @@ def test_serialize args, assert GTK::Entity.__reset_id__! args.state.player_one = "test" result = args.gtk.serialize_state args.state - assert.equal! result, "{:entity_id=>3, :tick_count=>-1, :player_one=>\"test\"}" + assert.equal! result, "{:entity_id=>3, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}" GTK::Entity.__reset_id__! args.gtk.write_file 'state.txt', '' result = args.gtk.serialize_state 'state.txt', args.state - assert.equal! result, "{:entity_id=>3, :tick_count=>-1, :player_one=>\"test\"}" + assert.equal! result, "{:entity_id=>3, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>\"test\"}" end def test_deserialize args, assert @@ -38,8 +38,9 @@ def test_strict_entity_serialization args, assert args.state.player_two = args.state.new_entity_strict(:player_strict, name: "Ken") serialized_state = args.gtk.serialize_state args.state - assert.equal! serialized_state, '{:entity_id=>1, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>3, :entity_name=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :name=>"Ken"}}' + assert.equal! serialized_state, '{:entity_id=>1, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_keys_by_ref=>{}, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>3, :entity_name=>:player_strict, :entity_type=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :entity_keys_by_ref=>{:entity_type=>:entity_name, :global_created_at_elapsed=>:created_at}, :name=>"Ken"}}' + GTK::Entity.__reset_id__! deserialize_state = args.gtk.deserialize_state serialized_state assert.equal! args.state.player_one.name, deserialize_state.player_one.name @@ -55,8 +56,9 @@ def test_strict_entity_serialization_with_nil args, assert args.state.player_two = args.state.new_entity_strict(:player_strict, name: "Ken", blood_type: nil) serialized_state = args.gtk.serialize_state args.state - assert.equal! serialized_state, '{:entity_id=>3, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>3, :entity_name=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :name=>"Ken", :blood_type=>nil}}' + assert.equal! serialized_state, '{:entity_id=>9, :entity_keys_by_ref=>{}, :tick_count=>-1, :player_one=>{:entity_id=>1, :entity_name=>:player, :entity_keys_by_ref=>{}, :entity_type=>:player, :created_at=>-1, :global_created_at=>-1, :name=>"Ryu"}, :player_two=>{:entity_id=>2, :entity_name=>:player_strict, :entity_type=>:player_strict, :created_at=>-1, :global_created_at_elapsed=>-1, :entity_strict=>true, :entity_keys_by_ref=>{:entity_type=>:entity_name, :global_created_at_elapsed=>:created_at}, :name=>"Ken", :blood_type=>nil}}' + GTK::Entity.__reset_id__! deserialized_state = args.gtk.deserialize_state serialized_state assert.equal! args.state.player_one.name, deserialized_state.player_one.name @@ -77,10 +79,39 @@ def test_multiple_strict_entities args, assert args.state.enemy = args.state.new_entity_strict(:enemy, name: "Bison", other_property: 'extra mean') serialized_state = args.gtk.serialize_state args.state + + GTK::Entity.__reset_id__! deserialized_state = args.gtk.deserialize_state serialized_state assert.equal! deserialized_state.player.name, "Ryu" assert.equal! deserialized_state.enemy.other_property, "extra mean" end +def test_by_reference_state args, assert + GTK::Entity.__reset_id__! + args.state.a = { name: "Jane Doe" } + args.state.b = args.state.a + assert.equal! args.state.a.object_id, args.state.b.object_id + serialized_state = args.gtk.serialize_state args.state + + GTK::Entity.__reset_id__! + deserialized_state = args.gtk.deserialize_state serialized_state + assert.equal! deserialized_state.a.object_id, deserialized_state.b.object_id +end + +def test_by_reference_state_strict_entities args, assert + GTK::Entity.__reset_id__! + args.state.a = { name: "Jane Doe" } + args.state.strict_entity = args.state.new_entity_strict(:couple) do |e| + e.one = args.state.new_entity_strict(:person, name: "Jane") + e.two = e.one + end + assert.equal! args.state.strict_entity.one, args.state.strict_entity.two + serialized_state = args.gtk.serialize_state args.state + + GTK::Entity.__reset_id__! + deserialized_state = args.gtk.deserialize_state serialized_state + assert.equal! deserialized_state.strict_entity.one, deserialized_state.strict_entity.two +end + $tests.start diff --git a/samples/12_c_extensions/01_basics/README.md b/samples/12_c_extensions/01_basics/README.md new file mode 100644 index 0000000..6993173 --- /dev/null +++ b/samples/12_c_extensions/01_basics/README.md @@ -0,0 +1,227 @@ +# C Extensions introduction + +This sample app serves as an introduction on how to write C 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 C Extensions +looks like. + +## Requirements + +In order to use C extensions you need a C compiler. We strongly recommend you +using [Clang](https://clang.llvm.org). + +### Windows + +To get Clang for Windows you can use the [official build](https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/LLVM-10.0.0-win64.exe). + +During installation, make sure to choose "Add LLVM to the system PATH for all +users", as on the screenshot  + +On Windows, you also need to install MinGW-w64 toolchain from [here](http://mingw-w64.org/doku.php/download/mingw-builds) +Set the installation directory to "C:\mingw-w64":  and on the next step choose the right version and the +other setting as on the screenshot: . + + +### Linux + +On Debian-based systems you can install Clang via `apt`. It is recommended you +install `clang-10`, but any other 'reasonably' fresh version should work just +fine: + +``` +> apt-get install clang-10 +> clang-10 --version +Apple clang version 10.0.0-4ubuntu1-18.04.2 +Target: x86_64-pc-linux-gnu +Thread model: posix +InstalledDir: /usr/bin +``` + +Raspberry Pi instructions are mostly the same as x86-64 Linux. Out of the box, +Clang can cross-compile for the Pi on any platform, but you'll need to set up +a proper sysroot, which can be complicated, and beyond the scope of this +document. The bold--and patient--can always compile on the Pi device directly, +too. + + +### 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 +``` + +### Android + +For Android, you can [install the Android NDK](https://developer.android.com/ndk/) +on Windows, Linux, or macOS. This lets you compile C code for the platform. You +won't be using Java here. + +DragonRuby ships with support for Android on arm32, arm64, x86, and x86-64. This might +be overkill, but it allows for maximum hardware compatibility. You should plan to +compile for all four architectures too, but of course this is optional. + + +## Hello World + +Let's craft the simplest possible C extension: a simple function that +calculates square of an integer. Put the following code into a file named +`ext.c` under `mygame` directory: + +``` +int square(int x) { + return x * x; +} +``` + +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` (or `dragonruby-bind.exe` if you are on Windows). + +Run the following command from Linux/macOS terminal: + +``` +> ./dragonruby-bind --output=mygame/ext-bind.c mygame/ext.c +``` + +Or from Windows Powershell: + +``` +> .\dragobruby-bind.exe --output=mygame\ext-bind.c mygame\ext.c +``` + +With the `ext-bind.c` in place you can compile your C extension into a dynamic +or shared library. Here is how you do it: + +On Windows: + +``` +clang -shared \ + --sysroot=C:\mingw-w64\mingw64^ + --target=x86_64-w64-mingw32 -fuse-ld=lld^ + -isystem include -I . -fPIC^ + -o mygame\native\windows-amd64\ext.dll mygame\ext-bind.c +``` + +On Linux: + +``` +> clang -shared \ + -isystem include -I . -fPIC \ + -o mygame/native/linux-amd64/ext.so mygame/ext-bind.c +``` + +On Raspberry Pi, on the device: + +``` +> clang -shared \ + -isystem include -I . -fPIC \ + -o mygame/native/linux-raspberrypi/ext.so mygame/ext-bind.c +``` + +On Raspberry Pi, with a cross-compiler: + +``` +> clang -shared --sysroot=/where/i/installed/a/sysroot \ + --target=arm-linux-gnueabihf -fuse-ld=lld \ + -isystem include -I . -fPIC \ + -o mygame/native/linux-raspberrypi/ext.so mygame/ext-bind.c +``` + +On macOS: + +``` +> clang -shared \ + -isystem include -I . -fPIC \ + -o mygame/native/macos/ext.dylib mygame/ext-bind.c +``` + +On Android: + +(This is what it looks like on Linux, change the appropriate parts for Windows, etc) + +``` +> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \ + --target=armv7-none-linux-androideabi26 \ + --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \ + --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \ + -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -march=armv7-a \ + -mthumb -fno-limit-debug-info -fPIC -shared \ + -o mygame/native/android-arm32/ext.so mygame/ext-bind.c + +> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \ + --target=aarch64-none-linux-android26 \ + --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \ + --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \ + -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 \ + -fno-limit-debug-info -fPIC -shared \ + -o mygame/native/android-arm64/ext.so mygame/ext-bind.c + +> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \ + --target=i686-none-linux-android26 \ + --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \ + --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \ + -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 \ + -fno-limit-debug-info -fPIC -shared \ + -o mygame/native/android-x86/ext.so mygame/ext-bind.c + +> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \ + --target=x86_64-none-linux-android26 \ + --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \ + --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \ + -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 \ + -fno-limit-debug-info -fPIC -shared \ + -o mygame/native/android-amd64/ext.so mygame/ext-bind.c +``` + + + +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/12_c_extensions/01_basics/app/main.rb b/samples/12_c_extensions/01_basics/app/main.rb index 537b9b1..2d61fa3 100644 --- a/samples/12_c_extensions/01_basics/app/main.rb +++ b/samples/12_c_extensions/01_basics/app/main.rb @@ -1,7 +1,10 @@ -$gtk.ffi_misc.gtk_dlopen("./samples/12_c_extensions/01_basics/build.dir/ext.lib") +$gtk.ffi_misc.gtk_dlopen("ext") include FFI::CExt def tick args - args.outputs.labels << [460, 600, "square(42) = #{square(42)}"] + 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/12_c_extensions/01_basics/c-basics-demo.png b/samples/12_c_extensions/01_basics/c-basics-demo.png Binary files differnew file mode 100644 index 0000000..715c0d9 --- /dev/null +++ b/samples/12_c_extensions/01_basics/c-basics-demo.png diff --git a/samples/12_c_extensions/01_basics/llvm-path.png b/samples/12_c_extensions/01_basics/llvm-path.png Binary files differnew file mode 100644 index 0000000..8bab1c9 --- /dev/null +++ b/samples/12_c_extensions/01_basics/llvm-path.png diff --git a/samples/12_c_extensions/01_basics/mingw-path.png b/samples/12_c_extensions/01_basics/mingw-path.png Binary files differnew file mode 100644 index 0000000..6e08419 --- /dev/null +++ b/samples/12_c_extensions/01_basics/mingw-path.png diff --git a/samples/12_c_extensions/01_basics/mingw-version.png b/samples/12_c_extensions/01_basics/mingw-version.png Binary files differnew file mode 100644 index 0000000..0fe6cef --- /dev/null +++ b/samples/12_c_extensions/01_basics/mingw-version.png diff --git a/samples/12_c_extensions/01_basics/native/ext-bindings.c b/samples/12_c_extensions/01_basics/native/ext-bindings.c new file mode 100644 index 0000000..c805961 --- /dev/null +++ b/samples/12_c_extensions/01_basics/native/ext-bindings.c @@ -0,0 +1,68 @@ +#include <mruby.h> +#include <string.h> +#include <assert.h> +#include <mruby/string.h> +#include <mruby/data.h> +#include <dragonruby.h> +#include "app/ext.c" + +// 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 + +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 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)) { + if (!(mrb_raise_f = (void (*)(mrb_state *, struct RClass *, const char *)) lookup("mrb_raise"))) return -1; + if (!(mrb_module_get_f = (struct RClass *(*)(mrb_state *, const char *)) lookup("mrb_module_get"))) 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_get_args_f = (mrb_int (*)(mrb_state *, mrb_args_format, ...)) lookup("mrb_get_args"))) return -1; + if (!(drb_free_foreign_object_f = (void (*)(mrb_state *, void *)) lookup("drb_free_foreign_object"))) 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_str_new_cstr_f = (mrb_value (*)(mrb_state *, const char *)) lookup("mrb_str_new_cstr"))) 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_module_function_f = (void (*)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec)) lookup("mrb_define_module_function"))) 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_module_get_under_f = (struct RClass *(*)(mrb_state *, struct RClass *, const char *)) lookup("mrb_module_get_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_class_method_f = (void (*)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec)) lookup("mrb_define_class_method"))) return -1; + return 0; +} diff --git a/samples/12_c_extensions/01_basics/pre.bat b/samples/12_c_extensions/01_basics/pre.bat index 3b3e27d..16befa3 100644 --- a/samples/12_c_extensions/01_basics/pre.bat +++ b/samples/12_c_extensions/01_basics/pre.bat @@ -1,4 +1,5 @@ set DRB_ROOT=..\..\..\ -md build.dir -%DRB_ROOT%\dragonruby-bind.exe --output=build.dir\ext-bind.c app\ext.c -clang -shared .\build.dir\ext-bind.c --sysroot=C:\mingw-w64\x86_64-8.1.0\mingw64 --target=x86_64-w64-mingw32 -fuse-ld=lld -isystem %DRB_ROOT%\include -I. -o build.dir\ext.lib +md native +md native\windows-amd64 +%DRB_ROOT%\dragonruby-bind.exe --output=native\ext-bind.c app\ext.c +clang -shared .\native\ext-bind.c --sysroot=C:\mingw-w64\mingw64 --target=x86_64-w64-mingw32 -fuse-ld=lld -isystem %DRB_ROOT%\include -I. -o native\windows-amd64\ext.dll diff --git a/samples/12_c_extensions/01_basics/pre.sh b/samples/12_c_extensions/01_basics/pre.sh index 8aa9eb7..07965a5 100755 --- a/samples/12_c_extensions/01_basics/pre.sh +++ b/samples/12_c_extensions/01_basics/pre.sh @@ -1,10 +1,19 @@ #!/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 build.dir +mkdir -p native/$PLATFORM -$DRB_ROOT/dragonruby-bind --output=build.dir/ext-bindings.c app/ext.c +$DRB_ROOT/dragonruby-bind --output=native/ext-bindings.c app/ext.c clang \ -isystem $DRB_ROOT/include -I. \ - -undefined dynamic_lookup \ - -fPIC -shared build.dir/ext-bindings.c -o build.dir/ext.lib + -fPIC -shared native/ext-bindings.c -o native/$PLATFORM/ext.$DLLEXT diff --git a/samples/12_c_extensions/02_intermediate/README.md b/samples/12_c_extensions/02_intermediate/README.md new file mode 100644 index 0000000..a597944 --- /dev/null +++ b/samples/12_c_extensions/02_intermediate/README.md @@ -0,0 +1,187 @@ +# C Extensions introduction + +This sample app is the next step on mastering C 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 C Extensions looks like. + +## Requirements + +Make sure you went through the first example '01_basics'. + +## Regular Expressions + +This sample shows how to use a third-party C library. We use a library for +regular expressions: https://github.com/kokke/tiny-regex-c. + +The library comes with two files `re.c` and `re.h`. We need to generate bindings +from the header file. By default, DragonRuby generates bindings under `CExt` +module. This can be overriden by `--ffi-module=` command line flag. + +Run the following command from Linux/macOS terminal: + +``` +> ./dragonruby-bind --output=mygame/re-bindings.c --ffi-module=RE mygame/re.h +``` + +Or from Windows Powershell: + +``` +> .\dragobruby-bind.exe --output=mygame\re-bindings.c --ffi-module=RE mygame\re.h +``` + +Now you can compile the library together with the generated bindings. + +On Windows: + +``` +clang -shared \ + --sysroot=C:\mingw-w64\mingw64^ + --target=x86_64-w64-mingw32 -fuse-ld=lld^ + -isystem include -I . -fPIC^ + -o mygame\native\windows-amd64\ext.dll mygame\re.c mygame\re-bindings.c +``` + +On Linux: + +``` +> clang -shared \ + -isystem include -I . -fPIC \ + -o mygame/native/linux-amd64/ext.so mygame/re-bindings.c mygame/re.c +``` + +On Raspberry Pi, on the device: + +``` +> clang -shared \ + -isystem include -I . -fPIC \ + -o mygame/native/linux-raspberrypi/ext.so mygame/re-bindings.c mygame/re.c +``` + +On Raspberry Pi, with a cross-compiler: + +``` +> clang -shared --sysroot=/where/i/installed/a/sysroot \ + --target=arm-linux-gnueabihf -fuse-ld=lld \ + -isystem include -I . -fPIC \ + -o mygame/native/linux-raspberrypi/ext.so mygame/re-bindings.c mygame/re.c +``` + +On macOS: + +``` +> clang -shared \ + -isystem include -I . -fPIC \ + -o mygame/native/macos/ext.dylib mygame/re-bindings.c mygame/re.c +``` + +On Android: + +(This is what it looks like on Linux, change the appropriate parts for Windows, etc) + +``` +> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \ + --target=armv7-none-linux-androideabi26 \ + --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \ + --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \ + -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -march=armv7-a \ + -mthumb -fno-limit-debug-info -fPIC -shared \ + -o mygame/native/android-arm32/ext.so mygame/re-bindings.c mygame/re.c + +> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \ + --target=aarch64-none-linux-android26 \ + --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \ + --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \ + -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 \ + -fno-limit-debug-info -fPIC -shared \ + -o mygame/native/android-arm64/ext.so mygame/re-bindings.c mygame/re.c + +> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \ + --target=i686-none-linux-android26 \ + --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \ + --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \ + -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 \ + -fno-limit-debug-info -fPIC -shared \ + -o mygame/native/android-x86/ext.so mygame/re-bindings.c mygame/re.c + +> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \ + --target=x86_64-none-linux-android26 \ + --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \ + --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \ + -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 \ + -fno-limit-debug-info -fPIC -shared \ + -o mygame/native/android-amd64/ext.so mygame/re-bindings.c mygame/re.c +``` + +Now, the C extension is available under the `FFI::RE` namespace. + +``` +$gtk.ffi_misc.gtk_dlopen("ext") +include FFI::RE +``` + +The `tiny-regex-c` exposes three functions, we are interested in the last one: + +```c +int re_match(const char* pattern, const char* text, int* matchlength); +``` + +The first argument is the regex pattern to search for. The second one is the +actual text we want to search through. The last argument is a pointer to an +integer: it stores the location of the last character found in the text. +The function returns the first location of the found pattern, or -1 if the +pattern is not found. The usage in DragonRuby is straightforward: + +```ruby +$gtk.ffi_misc.gtk_dlopen("ext") +include FFI::RE + +input = "<<Hello, DragonRiders!>>" +last = IntPointer.new +first = re_match("\\w+", input, last) +if first != -1 + puts "Found a word (#{first}, #{last.value}): '#{input.slice(first, last.value)}'" +end + +def tick args +end +``` + +When you run the game you should see `Found a word (2, 5): 'Hello'` on the console. + +Two things are worth mentioning. The C API requires `const char *` and `int *`. +In case of strings, DragonRuby does the transformation for you transparently. +In the case of `int *` you have to create the right pointer manually (`IntPointer.new`). +The good thing is that you don't need to free or deallocate this memory manually: +DragonRuby also does this for you transparently. + +With this knowledge, we can now create a slightly more complex function that +splits input into an array of words: + +```ruby +$gtk.ffi_misc.gtk_dlopen("ext") +include FFI::RE + +def split_words(input) + words = [] + last = IntPointer.new + re = re_compile("\\w+") + first = re_matchp(re, input, last) + while first != -1 + words << input.slice(first, last.value) + input = input.slice(last.value + first, input.length) + first = re_matchp(re, input, last) + end + words +end + +input = "<<Hello, DragonRiders!>>" + +def tick args + args.outputs.labels << [640, 500, split_words(input).join(' '), 5, 1] +end +``` + +Now, you should see a big label with "Hello DragonRiders" on it. + + diff --git a/samples/12_c_extensions/02_intermediate/app/main.rb b/samples/12_c_extensions/02_intermediate/app/main.rb new file mode 100644 index 0000000..2e3fd7c --- /dev/null +++ b/samples/12_c_extensions/02_intermediate/app/main.rb @@ -0,0 +1,19 @@ +$gtk.ffi_misc.gtk_dlopen("ext") +include FFI::RE + +def split_words(input) + words = [] + last = IntPointer.new + re = re_compile("\\w+") + first = re_matchp(re, input, last) + while first != -1 + words << input.slice(first, last.value) + input = input.slice(last.value + first, input.length) + first = re_matchp(re, input, last) + end + words +end + +def tick args + args.outputs.labels << [640, 500, split_words("hello, dragonriders!").join(' '), 5, 1] +end diff --git a/samples/12_c_extensions/02_intermediate/app/re.c b/samples/12_c_extensions/02_intermediate/app/re.c new file mode 100644 index 0000000..2d64d9f --- /dev/null +++ b/samples/12_c_extensions/02_intermediate/app/re.c @@ -0,0 +1,527 @@ +/* + * + * Mini regex-module inspired by Rob Pike's regex code described in: + * + * http://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html + * + * + * + * Supports: + * --------- + * '.' Dot, matches any character + * '^' Start anchor, matches beginning of string + * '$' End anchor, matches end of string + * '*' Asterisk, match zero or more (greedy) + * '+' Plus, match one or more (greedy) + * '?' Question, match zero or one (non-greedy) + * '[abc]' Character class, match if one of {'a', 'b', 'c'} + * '[^abc]' Inverted class, match if NOT one of {'a', 'b', 'c'} -- NOTE: feature is currently broken! + * '[a-zA-Z]' Character ranges, the character set of the ranges { a-z | A-Z } + * '\s' Whitespace, \t \f \r \n \v and spaces + * '\S' Non-whitespace + * '\w' Alphanumeric, [a-zA-Z0-9_] + * '\W' Non-alphanumeric + * '\d' Digits, [0-9] + * '\D' Non-digits + * + * + */ + + + +#include "re.h" +#include <stdio.h> + +/* Definitions: */ + +#define MAX_REGEXP_OBJECTS 30 /* Max number of regex symbols in expression. */ +#define MAX_CHAR_CLASS_LEN 40 /* Max length of character-class buffer in. */ + + +enum { UNUSED, DOT, BEGIN, END, QUESTIONMARK, STAR, PLUS, CHAR, CHAR_CLASS, INV_CHAR_CLASS, DIGIT, NOT_DIGIT, ALPHA, NOT_ALPHA, WHITESPACE, NOT_WHITESPACE, /* BRANCH */ }; + +typedef struct regex_t +{ + unsigned char type; /* CHAR, STAR, etc. */ + union + { + unsigned char ch; /* the character itself */ + unsigned char* ccl; /* OR a pointer to characters in class */ + }; +} regex_t; + + + +/* Private function declarations: */ +static int matchpattern(regex_t* pattern, const char* text, int* matchlength); +static int matchcharclass(char c, const char* str); +static int matchstar(regex_t p, regex_t* pattern, const char* text, int* matchlength); +static int matchplus(regex_t p, regex_t* pattern, const char* text, int* matchlength); +static int matchone(regex_t p, char c); +static int matchdigit(char c); +static int matchalpha(char c); +static int matchwhitespace(char c); +static int matchmetachar(char c, const char* str); +static int matchrange(char c, const char* str); +static int matchdot(char c); +static int ismetachar(char c); + + + +/* Public functions: */ +int re_match(const char* pattern, const char* text, int* matchlength) +{ + return re_matchp(re_compile(pattern), text, matchlength); +} + +int re_matchp(re_t pattern, const char* text, int* matchlength) +{ + *matchlength = 0; + if (pattern != 0) + { + if (pattern[0].type == BEGIN) + { + return ((matchpattern(&pattern[1], text, matchlength)) ? 0 : -1); + } + else + { + int idx = -1; + + do + { + idx += 1; + + if (matchpattern(pattern, text, matchlength)) + { + if (text[0] == '\0') + return -1; + + return idx; + } + } + while (*text++ != '\0'); + } + } + return -1; +} + +re_t re_compile(const char* pattern) +{ + /* The sizes of the two static arrays below substantiates the static RAM usage of this module. + MAX_REGEXP_OBJECTS is the max number of symbols in the expression. + MAX_CHAR_CLASS_LEN determines the size of buffer for chars in all char-classes in the expression. */ + static regex_t re_compiled[MAX_REGEXP_OBJECTS]; + static unsigned char ccl_buf[MAX_CHAR_CLASS_LEN]; + int ccl_bufidx = 1; + + char c; /* current char in pattern */ + int i = 0; /* index into pattern */ + int j = 0; /* index into re_compiled */ + + while (pattern[i] != '\0' && (j+1 < MAX_REGEXP_OBJECTS)) + { + c = pattern[i]; + + switch (c) + { + /* Meta-characters: */ + case '^': { re_compiled[j].type = BEGIN; } break; + case '$': { re_compiled[j].type = END; } break; + case '.': { re_compiled[j].type = DOT; } break; + case '*': { re_compiled[j].type = STAR; } break; + case '+': { re_compiled[j].type = PLUS; } break; + case '?': { re_compiled[j].type = QUESTIONMARK; } break; +/* case '|': { re_compiled[j].type = BRANCH; } break; <-- not working properly */ + + /* Escaped character-classes (\s \w ...): */ + case '\\': + { + if (pattern[i+1] != '\0') + { + /* Skip the escape-char '\\' */ + i += 1; + /* ... and check the next */ + switch (pattern[i]) + { + /* Meta-character: */ + case 'd': { re_compiled[j].type = DIGIT; } break; + case 'D': { re_compiled[j].type = NOT_DIGIT; } break; + case 'w': { re_compiled[j].type = ALPHA; } break; + case 'W': { re_compiled[j].type = NOT_ALPHA; } break; + case 's': { re_compiled[j].type = WHITESPACE; } break; + case 'S': { re_compiled[j].type = NOT_WHITESPACE; } break; + + /* Escaped character, e.g. '.' or '$' */ + default: + { + re_compiled[j].type = CHAR; + re_compiled[j].ch = pattern[i]; + } break; + } + } + /* '\\' as last char in pattern -> invalid regular expression. */ +/* + else + { + re_compiled[j].type = CHAR; + re_compiled[j].ch = pattern[i]; + } +*/ + } break; + + /* Character class: */ + case '[': + { + /* Remember where the char-buffer starts. */ + int buf_begin = ccl_bufidx; + + /* Look-ahead to determine if negated */ + if (pattern[i+1] == '^') + { + re_compiled[j].type = INV_CHAR_CLASS; + i += 1; /* Increment i to avoid including '^' in the char-buffer */ + if (pattern[i+1] == 0) /* incomplete pattern, missing non-zero char after '^' */ + { + return 0; + } + } + else + { + re_compiled[j].type = CHAR_CLASS; + } + + /* Copy characters inside [..] to buffer */ + while ( (pattern[++i] != ']') + && (pattern[i] != '\0')) /* Missing ] */ + { + if (pattern[i] == '\\') + { + if (ccl_bufidx >= MAX_CHAR_CLASS_LEN - 1) + { + //fputs("exceeded internal buffer!\n", stderr); + return 0; + } + if (pattern[i+1] == 0) /* incomplete pattern, missing non-zero char after '\\' */ + { + return 0; + } + ccl_buf[ccl_bufidx++] = pattern[i++]; + } + else if (ccl_bufidx >= MAX_CHAR_CLASS_LEN) + { + //fputs("exceeded internal buffer!\n", stderr); + return 0; + } + ccl_buf[ccl_bufidx++] = pattern[i]; + } + if (ccl_bufidx >= MAX_CHAR_CLASS_LEN) + { + /* Catches cases such as [00000000000000000000000000000000000000][ */ + //fputs("exceeded internal buffer!\n", stderr); + return 0; + } + /* Null-terminate string end */ + ccl_buf[ccl_bufidx++] = 0; + re_compiled[j].ccl = &ccl_buf[buf_begin]; + } break; + + /* Other characters: */ + default: + { + re_compiled[j].type = CHAR; + re_compiled[j].ch = c; + } break; + } + /* no buffer-out-of-bounds access on invalid patterns - see https://github.com/kokke/tiny-regex-c/commit/1a279e04014b70b0695fba559a7c05d55e6ee90b */ + if (pattern[i] == 0) + { + return 0; + } + + i += 1; + j += 1; + } + /* 'UNUSED' is a sentinel used to indicate end-of-pattern */ + re_compiled[j].type = UNUSED; + + return (re_t) re_compiled; +} + +void re_print(regex_t* pattern) +{ + const char* types[] = { "UNUSED", "DOT", "BEGIN", "END", "QUESTIONMARK", "STAR", "PLUS", "CHAR", "CHAR_CLASS", "INV_CHAR_CLASS", "DIGIT", "NOT_DIGIT", "ALPHA", "NOT_ALPHA", "WHITESPACE", "NOT_WHITESPACE", "BRANCH" }; + + int i; + int j; + char c; + for (i = 0; i < MAX_REGEXP_OBJECTS; ++i) + { + if (pattern[i].type == UNUSED) + { + break; + } + + printf("type: %s", types[pattern[i].type]); + if (pattern[i].type == CHAR_CLASS || pattern[i].type == INV_CHAR_CLASS) + { + printf(" ["); + for (j = 0; j < MAX_CHAR_CLASS_LEN; ++j) + { + c = pattern[i].ccl[j]; + if ((c == '\0') || (c == ']')) + { + break; + } + printf("%c", c); + } + printf("]"); + } + else if (pattern[i].type == CHAR) + { + printf(" '%c'", pattern[i].ch); + } + printf("\n"); + } +} + + + +/* Private functions: */ +static int matchdigit(char c) +{ + return ((c >= '0') && (c <= '9')); +} +static int matchalpha(char c) +{ + return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')); +} +static int matchwhitespace(char c) +{ + return ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r') || (c == '\f') || (c == '\v')); +} +static int matchalphanum(char c) +{ + return ((c == '_') || matchalpha(c) || matchdigit(c)); +} +static int matchrange(char c, const char* str) +{ + return ( (c != '-') + && (str[0] != '\0') + && (str[0] != '-') + && (str[1] == '-') + && (str[2] != '\0') + && ( (c >= str[0]) + && (c <= str[2]))); +} +static int matchdot(char c) +{ +#if defined(RE_DOT_MATCHES_NEWLINE) && (RE_DOT_MATCHES_NEWLINE == 1) + (void)c; + return 1; +#else + return c != '\n' && c != '\r'; +#endif +} +static int ismetachar(char c) +{ + return ((c == 's') || (c == 'S') || (c == 'w') || (c == 'W') || (c == 'd') || (c == 'D')); +} + +static int matchmetachar(char c, const char* str) +{ + switch (str[0]) + { + case 'd': return matchdigit(c); + case 'D': return !matchdigit(c); + case 'w': return matchalphanum(c); + case 'W': return !matchalphanum(c); + case 's': return matchwhitespace(c); + case 'S': return !matchwhitespace(c); + default: return (c == str[0]); + } +} + +static int matchcharclass(char c, const char* str) +{ + do + { + if (matchrange(c, str)) + { + return 1; + } + else if (str[0] == '\\') + { + /* Escape-char: increment str-ptr and match on next char */ + str += 1; + if (matchmetachar(c, str)) + { + return 1; + } + else if ((c == str[0]) && !ismetachar(c)) + { + return 1; + } + } + else if (c == str[0]) + { + if (c == '-') + { + return ((str[-1] == '\0') || (str[1] == '\0')); + } + else + { + return 1; + } + } + } + while (*str++ != '\0'); + + return 0; +} + +static int matchone(regex_t p, char c) +{ + switch (p.type) + { + case DOT: return matchdot(c); + case CHAR_CLASS: return matchcharclass(c, (const char*)p.ccl); + case INV_CHAR_CLASS: return !matchcharclass(c, (const char*)p.ccl); + case DIGIT: return matchdigit(c); + case NOT_DIGIT: return !matchdigit(c); + case ALPHA: return matchalphanum(c); + case NOT_ALPHA: return !matchalphanum(c); + case WHITESPACE: return matchwhitespace(c); + case NOT_WHITESPACE: return !matchwhitespace(c); + default: return (p.ch == c); + } +} + +static int matchstar(regex_t p, regex_t* pattern, const char* text, int* matchlength) +{ + int prelen = *matchlength; + const char* prepoint = text; + while ((text[0] != '\0') && matchone(p, *text)) + { + text++; + (*matchlength)++; + } + while (text >= prepoint) + { + if (matchpattern(pattern, text--, matchlength)) + return 1; + (*matchlength)--; + } + + *matchlength = prelen; + return 0; +} + +static int matchplus(regex_t p, regex_t* pattern, const char* text, int* matchlength) +{ + const char* prepoint = text; + while ((text[0] != '\0') && matchone(p, *text)) + { + text++; + (*matchlength)++; + } + while (text > prepoint) + { + if (matchpattern(pattern, text--, matchlength)) + return 1; + (*matchlength)--; + } + + return 0; +} + +static int matchquestion(regex_t p, regex_t* pattern, const char* text, int* matchlength) +{ + if (p.type == UNUSED) + return 1; + if (matchpattern(pattern, text, matchlength)) + return 1; + if (*text && matchone(p, *text++)) + { + if (matchpattern(pattern, text, matchlength)) + { + (*matchlength)++; + return 1; + } + } + return 0; +} + + +#if 0 + +/* Recursive matching */ +static int matchpattern(regex_t* pattern, const char* text, int *matchlength) +{ + int pre = *matchlength; + if ((pattern[0].type == UNUSED) || (pattern[1].type == QUESTIONMARK)) + { + return matchquestion(pattern[1], &pattern[2], text, matchlength); + } + else if (pattern[1].type == STAR) + { + return matchstar(pattern[0], &pattern[2], text, matchlength); + } + else if (pattern[1].type == PLUS) + { + return matchplus(pattern[0], &pattern[2], text, matchlength); + } + else if ((pattern[0].type == END) && pattern[1].type == UNUSED) + { + return text[0] == '\0'; + } + else if ((text[0] != '\0') && matchone(pattern[0], text[0])) + { + (*matchlength)++; + return matchpattern(&pattern[1], text+1); + } + else + { + *matchlength = pre; + return 0; + } +} + +#else + +/* Iterative matching */ +static int matchpattern(regex_t* pattern, const char* text, int* matchlength) +{ + int pre = *matchlength; + do + { + if ((pattern[0].type == UNUSED) || (pattern[1].type == QUESTIONMARK)) + { + return matchquestion(pattern[0], &pattern[2], text, matchlength); + } + else if (pattern[1].type == STAR) + { + return matchstar(pattern[0], &pattern[2], text, matchlength); + } + else if (pattern[1].type == PLUS) + { + return matchplus(pattern[0], &pattern[2], text, matchlength); + } + else if ((pattern[0].type == END) && pattern[1].type == UNUSED) + { + return (text[0] == '\0'); + } +/* Branching is not working properly + else if (pattern[1].type == BRANCH) + { + return (matchpattern(pattern, text) || matchpattern(&pattern[2], text)); + } +*/ + (*matchlength)++; + } + while ((text[0] != '\0') && matchone(*pattern++, *text++)); + + *matchlength = pre; + return 0; +} + +#endif diff --git a/samples/12_c_extensions/02_intermediate/app/re.h b/samples/12_c_extensions/02_intermediate/app/re.h new file mode 100644 index 0000000..fb2d002 --- /dev/null +++ b/samples/12_c_extensions/02_intermediate/app/re.h @@ -0,0 +1,65 @@ +/* + * + * Mini regex-module inspired by Rob Pike's regex code described in: + * + * http://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html + * + * + * + * Supports: + * --------- + * '.' Dot, matches any character + * '^' Start anchor, matches beginning of string + * '$' End anchor, matches end of string + * '*' Asterisk, match zero or more (greedy) + * '+' Plus, match one or more (greedy) + * '?' Question, match zero or one (non-greedy) + * '[abc]' Character class, match if one of {'a', 'b', 'c'} + * '[^abc]' Inverted class, match if NOT one of {'a', 'b', 'c'} -- NOTE: feature is currently broken! + * '[a-zA-Z]' Character ranges, the character set of the ranges { a-z | A-Z } + * '\s' Whitespace, \t \f \r \n \v and spaces + * '\S' Non-whitespace + * '\w' Alphanumeric, [a-zA-Z0-9_] + * '\W' Non-alphanumeric + * '\d' Digits, [0-9] + * '\D' Non-digits + * + * + */ + +#ifndef _TINY_REGEX_C +#define _TINY_REGEX_C + + +#ifndef RE_DOT_MATCHES_NEWLINE +/* Define to 0 if you DON'T want '.' to match '\r' + '\n' */ +#define RE_DOT_MATCHES_NEWLINE 1 +#endif + +#ifdef __cplusplus +extern "C"{ +#endif + + + +/* Typedef'd pointer to get abstract datatype. */ +typedef struct regex_t* re_t; + + +/* Compile regex string pattern to a regex_t-array. */ +re_t re_compile(const char* pattern); + + +/* Find matches of the compiled pattern inside text. */ +int re_matchp(re_t pattern, const char* text, int* matchlength); + + +/* Find matches of the txt pattern inside text (will compile automatically first). */ +int re_match(const char* pattern, const char* text, int* matchlength); + + +#ifdef __cplusplus +} +#endif + +#endif /* ifndef _TINY_REGEX_C */ diff --git a/samples/12_c_extensions/02_intermediate/c-regex-demo.png b/samples/12_c_extensions/02_intermediate/c-regex-demo.png Binary files differnew file mode 100644 index 0000000..19277c8 --- /dev/null +++ b/samples/12_c_extensions/02_intermediate/c-regex-demo.png diff --git a/samples/12_c_extensions/02_intermediate/license-for-regex-library.txt b/samples/12_c_extensions/02_intermediate/license-for-regex-library.txt new file mode 100644 index 0000000..cf1ab25 --- /dev/null +++ b/samples/12_c_extensions/02_intermediate/license-for-regex-library.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to <http://unlicense.org> diff --git a/samples/12_c_extensions/02_intermediate/license-for-sample.txt b/samples/12_c_extensions/02_intermediate/license-for-sample.txt new file mode 100644 index 0000000..100dcec --- /dev/null +++ b/samples/12_c_extensions/02_intermediate/license-for-sample.txt @@ -0,0 +1,9 @@ +Copyright 2019 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/12_c_extensions/02_intermediate/metadata/game_metadata.txt b/samples/12_c_extensions/02_intermediate/metadata/game_metadata.txt new file mode 100644 index 0000000..80ed5d1 --- /dev/null +++ b/samples/12_c_extensions/02_intermediate/metadata/game_metadata.txt @@ -0,0 +1,5 @@ +devid=dragonruby +devtitle=DragonRuby LLC +gameid=cintermediate +gametitle=C Extensions Intermediate +version=1.0 diff --git a/samples/12_c_extensions/02_intermediate/native/re-bindings.c b/samples/12_c_extensions/02_intermediate/native/re-bindings.c new file mode 100644 index 0000000..6aa5630 --- /dev/null +++ b/samples/12_c_extensions/02_intermediate/native/re-bindings.c @@ -0,0 +1,282 @@ +#include <mruby.h> +#include <string.h> +#include <assert.h> +#include <mruby/string.h> +#include <mruby/data.h> +#include <dragonruby.h> +#include "app/re.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 + +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); +} +struct drb_foreign_object_ZTSP7regex_t { + drb_foreign_object_kind kind; + struct regex_t *value; + int should_free; +}; +static mrb_data_type ForeignObjectType_ZTSP7regex_t = {"regex_t*", drb_free_foreign_object_indirect}; +static struct regex_t *drb_ffi__ZTSP7regex_t_FromRuby(mrb_state *state, mrb_value self) { + if (mrb_nil_p(self)) + return 0; + return ((struct drb_foreign_object_ZTSP7regex_t *)DATA_PTR(self))->value; +} +static mrb_value drb_ffi__ZTSP7regex_t_ToRuby(mrb_state *state, struct regex_t *value) { + struct drb_foreign_object_ZTSP7regex_t *ptr = calloc(1, sizeof(struct drb_foreign_object_ZTSP7regex_t)); + ptr->value = value; + ptr->kind = drb_foreign_object_kind_pointer; + struct RClass *FFI = mrb_module_get_f(state, "FFI"); + struct RClass *module = mrb_module_get_under_f(state, FFI, "RE"); + struct RClass *klass = mrb_class_get_under_f(state, module, "Regex_tPointer"); + struct RData *rdata = mrb_data_object_alloc_f(state, klass, ptr, &ForeignObjectType_ZTSP7regex_t); + return mrb_obj_value(rdata); +} +struct drb_foreign_object_ZTSPc { + drb_foreign_object_kind kind; + char *value; + int should_free; +}; +static mrb_data_type ForeignObjectType_ZTSPc = {"char*", drb_free_foreign_object_indirect}; +static char *drb_ffi__ZTSPc_FromRuby(mrb_state *state, mrb_value self) { + if (mrb_nil_p(self)) + return 0; + if (mrb_type(self) == MRB_TT_STRING) + return RSTRING_PTR(self); + return ((struct drb_foreign_object_ZTSPc *)DATA_PTR(self))->value; +} +static mrb_value drb_ffi__ZTSPc_ToRuby(mrb_state *state, char *value) { + struct drb_foreign_object_ZTSPc *ptr = calloc(1, sizeof(struct drb_foreign_object_ZTSPc)); + ptr->value = value; + ptr->kind = drb_foreign_object_kind_pointer; + struct RClass *FFI = mrb_module_get_f(state, "FFI"); + struct RClass *module = mrb_module_get_under_f(state, FFI, "RE"); + struct RClass *klass = mrb_class_get_under_f(state, module, "CharPointer"); + struct RData *rdata = mrb_data_object_alloc_f(state, klass, ptr, &ForeignObjectType_ZTSPc); + return mrb_obj_value(rdata); +} +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); +} +struct drb_foreign_object_ZTSPi { + drb_foreign_object_kind kind; + int *value; + int should_free; +}; +static mrb_data_type ForeignObjectType_ZTSPi = {"int*", drb_free_foreign_object_indirect}; +static int *drb_ffi__ZTSPi_FromRuby(mrb_state *state, mrb_value self) { + if (mrb_nil_p(self)) + return 0; + return ((struct drb_foreign_object_ZTSPi *)DATA_PTR(self))->value; +} +static mrb_value drb_ffi__ZTSPi_ToRuby(mrb_state *state, int *value) { + struct drb_foreign_object_ZTSPi *ptr = calloc(1, sizeof(struct drb_foreign_object_ZTSPi)); + ptr->value = value; + ptr->kind = drb_foreign_object_kind_pointer; + struct RClass *FFI = mrb_module_get_f(state, "FFI"); + struct RClass *module = mrb_module_get_under_f(state, FFI, "RE"); + struct RClass *klass = mrb_class_get_under_f(state, module, "IntPointer"); + struct RData *rdata = mrb_data_object_alloc_f(state, klass, ptr, &ForeignObjectType_ZTSPi); + return mrb_obj_value(rdata); +} +static char drb_ffi__ZTSc_FromRuby(mrb_state *state, mrb_value self) { + return mrb_fixnum(self); +} +static mrb_value drb_ffi__ZTSc_ToRuby(mrb_state *state, char value) { + return mrb_fixnum_value(value); +} +static mrb_value drb_ffi__ZTSP7regex_t_New(mrb_state *mrb, mrb_value self) { + mrb_raise_f(mrb, mrb_exc_get_f(mrb, "RuntimeError"), "Cannot allocate pointer of incomplete type"); + return mrb_nil_value(); +} +static mrb_value drb_ffi__ZTSP7regex_t_GetValue(mrb_state *mrb, mrb_value value) { + mrb_raise_f(mrb, mrb_exc_get_f(mrb, "RuntimeError"), "Cannot access value of incomplete type"); + return mrb_nil_value(); +} +static mrb_value drb_ffi__ZTSP7regex_t_IsNil(mrb_state *state, mrb_value self) { + if (drb_ffi__ZTSP7regex_t_FromRuby(state, self) == 0) + return mrb_true_value(); + else + return mrb_false_value(); +} +static mrb_value drb_ffi__ZTSP7regex_t_GetAt(mrb_state *mrb, mrb_value self) { + mrb_raise_f(mrb, mrb_exc_get_f(mrb, "RuntimeError"), "Cannot access value of incomplete type"); + return mrb_nil_value(); +} +static mrb_value drb_ffi__ZTSP7regex_t_SetAt(mrb_state *mrb, mrb_value self) { + mrb_raise_f(mrb, mrb_exc_get_f(mrb, "RuntimeError"), "Cannot change value of incomplete type"); + return mrb_nil_value(); +} +static mrb_value drb_ffi__ZTSPc_New(mrb_state *mrb, mrb_value self) { + struct drb_foreign_object_ZTSPc *ptr = calloc(1, sizeof(struct drb_foreign_object_ZTSPc)); + ptr->kind = drb_foreign_object_kind_pointer; + ptr->value = calloc(1, sizeof(char)); + ptr->should_free = 1; + struct RClass *FFI = mrb_module_get_f(mrb, "FFI"); + struct RClass *module = mrb_module_get_under_f(mrb, FFI, "RE"); + struct RClass *klass = mrb_class_get_under_f(mrb, module, "CharPointer"); + struct RData *rdata = mrb_data_object_alloc_f(mrb, klass, ptr, &ForeignObjectType_ZTSPc); + return mrb_obj_value(rdata); +} +static mrb_value drb_ffi__ZTSPc_GetValue(mrb_state *mrb, mrb_value value) { + return drb_ffi__ZTSc_ToRuby(mrb, *drb_ffi__ZTSPc_FromRuby(mrb, value)); +} +static mrb_value drb_ffi__ZTSPc_IsNil(mrb_state *state, mrb_value self) { + if (drb_ffi__ZTSPc_FromRuby(state, self) == 0) + return mrb_true_value(); + else + return mrb_false_value(); +} +static mrb_value drb_ffi__ZTSPc_GetAt(mrb_state *mrb, mrb_value self) { + mrb_value *args = 0; + mrb_int argc = 0; + mrb_get_args_f(mrb, "*", &args, &argc); + int index = drb_ffi__ZTSi_FromRuby(mrb, args[0]); + return drb_ffi__ZTSc_ToRuby(mrb, drb_ffi__ZTSPc_FromRuby(mrb, self)[index]); +} +static mrb_value drb_ffi__ZTSPc_SetAt(mrb_state *mrb, mrb_value self) { + mrb_value *args = 0; + mrb_int argc = 0; + mrb_get_args_f(mrb, "*", &args, &argc); + int index = drb_ffi__ZTSi_FromRuby(mrb, args[0]); + char new_value = drb_ffi__ZTSc_FromRuby(mrb, args[1]); + drb_ffi__ZTSPc_FromRuby(mrb, self)[index] = new_value; + return mrb_nil_value(); +} +static mrb_value drb_ffi__ZTSPc_GetString(mrb_state *state, mrb_value self) { + return mrb_str_new_cstr_f(state, drb_ffi__ZTSPc_FromRuby(state, self)); +} +static mrb_value drb_ffi__ZTSPi_New(mrb_state *mrb, mrb_value self) { + struct drb_foreign_object_ZTSPi *ptr = calloc(1, sizeof(struct drb_foreign_object_ZTSPi)); + ptr->kind = drb_foreign_object_kind_pointer; + ptr->value = calloc(1, sizeof(int)); + ptr->should_free = 1; + struct RClass *FFI = mrb_module_get_f(mrb, "FFI"); + struct RClass *module = mrb_module_get_under_f(mrb, FFI, "RE"); + struct RClass *klass = mrb_class_get_under_f(mrb, module, "IntPointer"); + struct RData *rdata = mrb_data_object_alloc_f(mrb, klass, ptr, &ForeignObjectType_ZTSPi); + return mrb_obj_value(rdata); +} +static mrb_value drb_ffi__ZTSPi_GetValue(mrb_state *mrb, mrb_value value) { + return drb_ffi__ZTSi_ToRuby(mrb, *drb_ffi__ZTSPi_FromRuby(mrb, value)); +} +static mrb_value drb_ffi__ZTSPi_IsNil(mrb_state *state, mrb_value self) { + if (drb_ffi__ZTSPi_FromRuby(state, self) == 0) + return mrb_true_value(); + else + return mrb_false_value(); +} +static mrb_value drb_ffi__ZTSPi_GetAt(mrb_state *mrb, mrb_value self) { + mrb_value *args = 0; + mrb_int argc = 0; + mrb_get_args_f(mrb, "*", &args, &argc); + int index = drb_ffi__ZTSi_FromRuby(mrb, args[0]); + return drb_ffi__ZTSi_ToRuby(mrb, drb_ffi__ZTSPi_FromRuby(mrb, self)[index]); +} +static mrb_value drb_ffi__ZTSPi_SetAt(mrb_state *mrb, mrb_value self) { + mrb_value *args = 0; + mrb_int argc = 0; + mrb_get_args_f(mrb, "*", &args, &argc); + int index = drb_ffi__ZTSi_FromRuby(mrb, args[0]); + int new_value = drb_ffi__ZTSi_FromRuby(mrb, args[1]); + drb_ffi__ZTSPi_FromRuby(mrb, self)[index] = new_value; + return mrb_nil_value(); +} +static mrb_value drb_ffi_re_compile_Binding(mrb_state *state, mrb_value value) { + mrb_value *args = 0; + mrb_int argc = 0; + mrb_get_args_f(state, "*", &args, &argc); + char *pattern_0 = drb_ffi__ZTSPc_FromRuby(state, args[0]); + struct regex_t *ret_val = re_compile(pattern_0); + return drb_ffi__ZTSP7regex_t_ToRuby(state, ret_val); +} +static mrb_value drb_ffi_re_matchp_Binding(mrb_state *state, mrb_value value) { + mrb_value *args = 0; + mrb_int argc = 0; + mrb_get_args_f(state, "*", &args, &argc); + struct regex_t *pattern_0 = drb_ffi__ZTSP7regex_t_FromRuby(state, args[0]); + char *text_1 = drb_ffi__ZTSPc_FromRuby(state, args[1]); + int *matchlength_2 = drb_ffi__ZTSPi_FromRuby(state, args[2]); + int ret_val = re_matchp(pattern_0, text_1, matchlength_2); + return drb_ffi__ZTSi_ToRuby(state, ret_val); +} +static mrb_value drb_ffi_re_match_Binding(mrb_state *state, mrb_value value) { + mrb_value *args = 0; + mrb_int argc = 0; + mrb_get_args_f(state, "*", &args, &argc); + char *pattern_0 = drb_ffi__ZTSPc_FromRuby(state, args[0]); + char *text_1 = drb_ffi__ZTSPc_FromRuby(state, args[1]); + int *matchlength_2 = drb_ffi__ZTSPi_FromRuby(state, args[2]); + int ret_val = re_match(pattern_0, text_1, matchlength_2); + 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, "RE"); + struct RClass *object_class = state->object_class; + mrb_define_module_function_f(state, module, "re_compile", drb_ffi_re_compile_Binding, MRB_ARGS_REQ(1)); + mrb_define_module_function_f(state, module, "re_matchp", drb_ffi_re_matchp_Binding, MRB_ARGS_REQ(3)); + mrb_define_module_function_f(state, module, "re_match", drb_ffi_re_match_Binding, MRB_ARGS_REQ(3)); + struct RClass *Regex_tPointerClass = mrb_define_class_under_f(state, module, "Regex_tPointer", object_class); + mrb_define_class_method_f(state, Regex_tPointerClass, "new", drb_ffi__ZTSP7regex_t_New, MRB_ARGS_REQ(0)); + mrb_define_method_f(state, Regex_tPointerClass, "value", drb_ffi__ZTSP7regex_t_GetValue, MRB_ARGS_REQ(0)); + mrb_define_method_f(state, Regex_tPointerClass, "[]", drb_ffi__ZTSP7regex_t_GetAt, MRB_ARGS_REQ(1)); + mrb_define_method_f(state, Regex_tPointerClass, "[]=", drb_ffi__ZTSP7regex_t_SetAt, MRB_ARGS_REQ(2)); + mrb_define_method_f(state, Regex_tPointerClass, "nil?", drb_ffi__ZTSP7regex_t_IsNil, MRB_ARGS_REQ(0)); + struct RClass *CharPointerClass = mrb_define_class_under_f(state, module, "CharPointer", object_class); + mrb_define_class_method_f(state, CharPointerClass, "new", drb_ffi__ZTSPc_New, MRB_ARGS_REQ(0)); + mrb_define_method_f(state, CharPointerClass, "value", drb_ffi__ZTSPc_GetValue, MRB_ARGS_REQ(0)); + mrb_define_method_f(state, CharPointerClass, "[]", drb_ffi__ZTSPc_GetAt, MRB_ARGS_REQ(1)); + mrb_define_method_f(state, CharPointerClass, "[]=", drb_ffi__ZTSPc_SetAt, MRB_ARGS_REQ(2)); + mrb_define_method_f(state, CharPointerClass, "nil?", drb_ffi__ZTSPc_IsNil, MRB_ARGS_REQ(0)); + mrb_define_method_f(state, CharPointerClass, "str", drb_ffi__ZTSPc_GetString, MRB_ARGS_REQ(0)); + struct RClass *IntPointerClass = mrb_define_class_under_f(state, module, "IntPointer", object_class); + mrb_define_class_method_f(state, IntPointerClass, "new", drb_ffi__ZTSPi_New, MRB_ARGS_REQ(0)); + mrb_define_method_f(state, IntPointerClass, "value", drb_ffi__ZTSPi_GetValue, MRB_ARGS_REQ(0)); + mrb_define_method_f(state, IntPointerClass, "[]", drb_ffi__ZTSPi_GetAt, MRB_ARGS_REQ(1)); + mrb_define_method_f(state, IntPointerClass, "[]=", drb_ffi__ZTSPi_SetAt, MRB_ARGS_REQ(2)); + mrb_define_method_f(state, IntPointerClass, "nil?", drb_ffi__ZTSPi_IsNil, MRB_ARGS_REQ(0)); +} +static int drb_ffi_init_indirect_functions(void *(*lookup)(const char *fnname)) { + 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; + 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_class_method_f = (void (*)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec)) lookup("mrb_define_class_method"))) 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_module_get_under_f = (struct RClass *(*)(mrb_state *, struct RClass *, const char *)) lookup("mrb_module_get_under"))) return -1; + if (!(drb_free_foreign_object_f = (void (*)(mrb_state *, void *)) lookup("drb_free_foreign_object"))) 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_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_define_method_f = (void (*)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec)) lookup("mrb_define_method"))) return -1; + if (!(mrb_exc_get_f = (struct RClass *(*)(mrb_state *, const char *)) lookup("mrb_exc_get"))) 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; + return 0; +} diff --git a/samples/12_c_extensions/02_intermediate/pre.bat b/samples/12_c_extensions/02_intermediate/pre.bat new file mode 100644 index 0000000..2b4ea2e --- /dev/null +++ b/samples/12_c_extensions/02_intermediate/pre.bat @@ -0,0 +1,5 @@ +set DRB_ROOT=..\..\..\ +md native +md native\windows-amd64 +%DRB_ROOT%\dragonruby-bind.exe --ffi-module=RE --output=native\re-bindings.c app\re.h +clang -shared .\native\re-bindings.c .\app\re.c --sysroot=C:\mingw-w64\mingw64 --target=x86_64-w64-mingw32 -fuse-ld=lld -isystem %DRB_ROOT%\include -I. -o native\windows-amd64\ext.dll diff --git a/samples/12_c_extensions/02_intermediate/pre.sh b/samples/12_c_extensions/02_intermediate/pre.sh new file mode 100755 index 0000000..405f205 --- /dev/null +++ b/samples/12_c_extensions/02_intermediate/pre.sh @@ -0,0 +1,19 @@ +#!/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 + +$DRB_ROOT/dragonruby-bind -ffi-module=RE --output=native/re-bindings.c app/re.h +clang \ + -isystem $DRB_ROOT/include -I. \ + -fPIC -shared app/re.c native/re-bindings.c -o native/$PLATFORM/ext.$DLLEXT + |
