dama 0.1.0-x86_64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +227 -0
  4. data/dama-logo.svg +91 -0
  5. data/exe/dama +4 -0
  6. data/lib/dama/animation.rb +66 -0
  7. data/lib/dama/asset_cache.rb +56 -0
  8. data/lib/dama/audio.rb +47 -0
  9. data/lib/dama/auto_loader.rb +54 -0
  10. data/lib/dama/backend/base.rb +137 -0
  11. data/lib/dama/backend/native/ffi_bindings.rb +122 -0
  12. data/lib/dama/backend/native.rb +191 -0
  13. data/lib/dama/backend/web.rb +199 -0
  14. data/lib/dama/backend.rb +13 -0
  15. data/lib/dama/camera.rb +68 -0
  16. data/lib/dama/cli/new_project.rb +112 -0
  17. data/lib/dama/cli/release.rb +45 -0
  18. data/lib/dama/cli.rb +22 -0
  19. data/lib/dama/colors.rb +30 -0
  20. data/lib/dama/command_buffer.rb +83 -0
  21. data/lib/dama/component/attribute_definition.rb +13 -0
  22. data/lib/dama/component/attribute_set.rb +32 -0
  23. data/lib/dama/component.rb +28 -0
  24. data/lib/dama/configuration.rb +18 -0
  25. data/lib/dama/debug/frame_controller.rb +35 -0
  26. data/lib/dama/debug/screenshot_tool.rb +19 -0
  27. data/lib/dama/debug.rb +4 -0
  28. data/lib/dama/event_bus.rb +47 -0
  29. data/lib/dama/game/builder.rb +31 -0
  30. data/lib/dama/game/loop.rb +44 -0
  31. data/lib/dama/game.rb +88 -0
  32. data/lib/dama/geometry/circle.rb +28 -0
  33. data/lib/dama/geometry/rect.rb +16 -0
  34. data/lib/dama/geometry/sprite.rb +18 -0
  35. data/lib/dama/geometry/triangle.rb +13 -0
  36. data/lib/dama/geometry.rb +4 -0
  37. data/lib/dama/input/keyboard_state.rb +44 -0
  38. data/lib/dama/input/mouse_state.rb +45 -0
  39. data/lib/dama/input.rb +38 -0
  40. data/lib/dama/keys.rb +67 -0
  41. data/lib/dama/native/libdama_native.so +0 -0
  42. data/lib/dama/node/component_slot.rb +18 -0
  43. data/lib/dama/node/draw_context.rb +96 -0
  44. data/lib/dama/node.rb +139 -0
  45. data/lib/dama/physics/body.rb +57 -0
  46. data/lib/dama/physics/collider.rb +152 -0
  47. data/lib/dama/physics/collision.rb +15 -0
  48. data/lib/dama/physics/world.rb +125 -0
  49. data/lib/dama/physics.rb +4 -0
  50. data/lib/dama/registry/class_resolver.rb +48 -0
  51. data/lib/dama/registry.rb +21 -0
  52. data/lib/dama/release/archiver.rb +100 -0
  53. data/lib/dama/release/defaults/icon.icns +0 -0
  54. data/lib/dama/release/defaults/icon.ico +0 -0
  55. data/lib/dama/release/defaults/icon.png +0 -0
  56. data/lib/dama/release/dylib_relinker.rb +95 -0
  57. data/lib/dama/release/game_file_copier.rb +35 -0
  58. data/lib/dama/release/game_metadata.rb +61 -0
  59. data/lib/dama/release/icon_provider.rb +36 -0
  60. data/lib/dama/release/native_builder.rb +44 -0
  61. data/lib/dama/release/packager/linux.rb +62 -0
  62. data/lib/dama/release/packager/macos.rb +99 -0
  63. data/lib/dama/release/packager/web.rb +32 -0
  64. data/lib/dama/release/packager/windows.rb +61 -0
  65. data/lib/dama/release/packager.rb +9 -0
  66. data/lib/dama/release/platform_detector.rb +23 -0
  67. data/lib/dama/release/ruby_bundler.rb +163 -0
  68. data/lib/dama/release/stdlib_trimmer.rb +133 -0
  69. data/lib/dama/release/template_renderer.rb +40 -0
  70. data/lib/dama/release/templates/info_plist.xml.erb +19 -0
  71. data/lib/dama/release/templates/launcher_linux.sh.erb +10 -0
  72. data/lib/dama/release/templates/launcher_macos.sh.erb +10 -0
  73. data/lib/dama/release/templates/launcher_windows.bat.erb +11 -0
  74. data/lib/dama/release.rb +7 -0
  75. data/lib/dama/scene/composer.rb +65 -0
  76. data/lib/dama/scene.rb +233 -0
  77. data/lib/dama/scene_graph/class_index.rb +26 -0
  78. data/lib/dama/scene_graph/group_node.rb +27 -0
  79. data/lib/dama/scene_graph/instance_node.rb +30 -0
  80. data/lib/dama/scene_graph/path_selector.rb +25 -0
  81. data/lib/dama/scene_graph/query.rb +34 -0
  82. data/lib/dama/scene_graph/tag_index.rb +26 -0
  83. data/lib/dama/scene_graph/tree.rb +65 -0
  84. data/lib/dama/scene_graph.rb +4 -0
  85. data/lib/dama/sprite_sheet.rb +36 -0
  86. data/lib/dama/tween/easing.rb +31 -0
  87. data/lib/dama/tween/lerp.rb +35 -0
  88. data/lib/dama/tween/manager.rb +28 -0
  89. data/lib/dama/tween.rb +4 -0
  90. data/lib/dama/version.rb +3 -0
  91. data/lib/dama/vertex_batch.rb +35 -0
  92. data/lib/dama/web/entry.rb +79 -0
  93. data/lib/dama/web/static/index.html +142 -0
  94. data/lib/dama/web_builder.rb +232 -0
  95. data/lib/dama.rb +42 -0
  96. metadata +186 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 14c7877e7cada9427d3f09b1e191b57ff91f44e0d7d95bda7967a6d2a0ba6969
4
+ data.tar.gz: 6441a240b6637c45c54fbb1b7418780038ea024a97233a2ac11c883644690a19
5
+ SHA512:
6
+ metadata.gz: 66edc610afd44d66c6a775d8157f236e1fca18197a4331875dbedd03c4473431e7059872651a9c27e5765c44c51354435b26f66875555f5f6ca5ca29147d357e
7
+ data.tar.gz: 389de5e6386731517d326d284a20369c265f6682f90495eed2b94d4b0f6466b0a89b08c4f1e4111c1528ffc80328eb420fc34c08297e364cb85d5ca9101a078c
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Caiubi Fonseca
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,227 @@
1
+ <div align="center">
2
+ <img src="dama-logo.svg" alt="dama-rb" width="120">
3
+ </div>
4
+
5
+ <h1 align="center">dama-rb</h1>
6
+
7
+ <p align="center">
8
+ A cross-platform 2D game engine with a Ruby DSL and Rust rendering backend.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <strong>Write games in Ruby. Render with wgpu. Run native or in the browser.</strong>
13
+ <br />
14
+ <a href="https://github.com/caiubi/dama-rb">View Demo</a>
15
+ </p>
16
+
17
+ ---
18
+
19
+ dama-rb lets you build 2D games using an expressive Ruby DSL. The engine handles rendering via a Rust/wgpu backend (Metal, Vulkan, DX12, WebGPU), so your game code is pure Ruby while GPU-accelerated graphics run at native speed.
20
+
21
+ The name "dama" is inspired by the Japanese word for "orb" (玉) and the Portuguese word for the "checkers" game (dama).
22
+
23
+ ## Screenshots
24
+
25
+ <p align="center">
26
+ <img src="docs/checkers_screenshot.png" alt="Checkers" width="380">
27
+ <img src="docs/breakout_screenshot.png" alt="Breakout" width="380">
28
+ </p>
29
+
30
+ ## Features
31
+
32
+ - **Ruby DSL** — Standard CRuby 3.4 — bring your gems, your tools, your workflow
33
+ - **Rust/wgpu renderer** — GPU-accelerated shapes, text, sprites, shaders, and textures
34
+ - **Cross-platform** — Native (macOS, Linux, Windows) and web (WebGPU via ruby.wasm)
35
+ - **Input handling** — Keyboard, mouse (gamepad support is on the [roadmap](#roadmap))
36
+ - **Sprite rendering** — Draw textures from your assets folder
37
+ - **Shader support** 🔥 — Write custom fragment shaders in WGSL (basic functions supported)
38
+ - **Sound effects** — Play audio files within your game
39
+ - **Physics** — AABB collision detection with a simple API (Rapier2D integration is on the [roadmap](#roadmap))
40
+ - **HiDPI/Retina** — Sharp rendering on high-density displays
41
+ - **100% test coverage** — RSpec with headless native backend integration tests (Web build pipeline coverage is on the [roadmap](#roadmap))
42
+ - **MIT licensed** — Every feature, for everyone, forever
43
+
44
+ ## Build Your First Game
45
+
46
+ ### 1. Create a project
47
+
48
+ ```bash
49
+ mkdir my_game && cd my_game
50
+ bundle init
51
+ bundle add dama
52
+ ```
53
+
54
+ ### 2. Scaffold the game
55
+
56
+ ```bash
57
+ bundle exec dama new
58
+ ```
59
+
60
+ This generates a playable starter project:
61
+
62
+ ```
63
+ my_game/
64
+ ├── bin/dama # game launcher
65
+ ├── config.rb # game settings (resolution, title, start scene)
66
+ ├── game/
67
+ │ ├── components/transform.rb # position data
68
+ │ ├── nodes/player.rb # entity with drawing
69
+ │ └── scenes/main_scene.rb # composes nodes, handles input
70
+ └── assets/ # images, sounds, shaders
71
+ ```
72
+
73
+ ### 3. Run it
74
+
75
+ ```bash
76
+ bin/dama # native window
77
+ bin/dama web # browser (WebGPU)
78
+ ```
79
+
80
+ You'll see a red circle you can move with the arrow keys.
81
+
82
+ ### 4. How it works
83
+
84
+ dama-rb uses a **Component → Node → Scene** architecture:
85
+
86
+ ```ruby
87
+ # Components hold pure data
88
+ class Transform < Dama::Component
89
+ attribute :x, default: 0.0
90
+ attribute :y, default: 0.0
91
+ end
92
+
93
+ # Nodes are entities — they own components and define drawing
94
+ class Player < Dama::Node
95
+ component Transform, as: :transform, x: 400.0, y: 300.0
96
+
97
+ draw do
98
+ circle(transform.x, transform.y, 20.0, **Dama::Colors::RED.to_h)
99
+ end
100
+ end
101
+
102
+ # Scenes compose nodes and handle game logic
103
+ class MainScene < Dama::Scene
104
+ compose do
105
+ add Player, as: :hero
106
+ end
107
+
108
+ update do |dt, input|
109
+ hero.transform.x += 200.0 * dt if input.right?
110
+ hero.transform.x -= 200.0 * dt if input.left?
111
+ end
112
+ end
113
+
114
+ # config.rb boots the game — must define a GAME constant
115
+ GAME = Dama::Game.new do
116
+ settings resolution: [800, 600], title: "My Game"
117
+ start_scene MainScene
118
+ end
119
+ ```
120
+
121
+ ### 5. Make it yours
122
+
123
+ - Edit `game/scenes/main_scene.rb` to change game logic
124
+ - Add new nodes in `game/nodes/`
125
+ - Add new components in `game/components/`
126
+ - Put images, sounds, and shaders in `assets/`
127
+ - See the [examples](examples/) for physics, sprites, shaders, and scene transitions
128
+
129
+ ## Architecture
130
+
131
+ ```
132
+ Ruby DSL (your game code)
133
+
134
+ Dama Engine (Ruby)
135
+ ↓ FFI / JS bridge
136
+ Rust Backend (wgpu)
137
+
138
+ GPU (Metal / Vulkan / WebGPU)
139
+ ```
140
+
141
+ - **Ruby** handles game logic, scene graph, components, input, and the update loop
142
+ - **Rust** handles window management (winit), GPU rendering (wgpu), text (glyphon), and screenshots
143
+ - **Native**: Ruby calls Rust via FFI (cdylib)
144
+ - **Web**: Ruby runs in ruby.wasm, calls Rust wasm via JS bridge
145
+
146
+ ## Drawing Primitives
147
+
148
+ ```ruby
149
+ draw do
150
+ rect(x, y, w, h, r:, g:, b:, a:)
151
+ circle(cx, cy, radius, r:, g:, b:, a:)
152
+ triangle(x1, y1, x2, y2, x3, y3, r:, g:, b:, a:)
153
+ text("Hello", x, y, size: 24.0, r:, g:, b:, a:)
154
+ sprite(texture_handle, x, y, w, h)
155
+ end
156
+ ```
157
+
158
+ ## Examples
159
+
160
+ - **[demo](examples/demo/)** — Shapes, sprites, FPS overlay, keyboard input
161
+ - **[checkers](examples/checkers/)** — Full 8x8 checkers game with piece selection, captures, king promotion, and scene transitions
162
+ - **[breakout](examples/breakout/)** — Brick breaker with physics, custom shaders, and score tracking
163
+
164
+ ## Development
165
+
166
+ ### Prerequisites
167
+
168
+ - Ruby 3.4+
169
+ - Rust (stable, via rustup)
170
+ - wasm-bindgen-cli (`cargo install wasm-bindgen-cli`)
171
+ - npm (for downloading ruby.wasm base binary — web builds only)
172
+
173
+ > **Note:** Web builds require `npm` (to download the ruby.wasm base binary) and `wasm-bindgen-cli`.
174
+
175
+ ### Setup
176
+
177
+ ```bash
178
+ git clone https://github.com/caiubi/dama-rb
179
+ cd dama-rb
180
+ bundle install
181
+ ```
182
+
183
+ ### Tests
184
+
185
+ ```bash
186
+ bundle exec rspec # Ruby specs (builds Rust automatically)
187
+ bundle exec rubocop # Ruby linting
188
+ cd ext/dama_native && cargo test # Rust tests
189
+ cd ext/dama_native && cargo clippy # Rust linting
190
+ ```
191
+
192
+ ### Run examples
193
+
194
+ ```bash
195
+ cd examples/checkers && bin/dama # Native
196
+ cd examples/checkers && bin/dama web # Browser
197
+ ```
198
+
199
+ ## Roadmap
200
+
201
+ ### Rapier2D Physics
202
+ - [ ] Replace AABB with Rapier2D behind Cargo feature flag
203
+ - [ ] Continuous collision detection, joints, raycasting
204
+
205
+ ### Game Framework Features
206
+ - [ ] Particle system
207
+ - [ ] UI framework (Button, Label, Panel)
208
+ - [ ] Tilemap (Tiled JSON format)
209
+ - [ ] Scene transitions (fade in/out)
210
+ - [ ] Save/Load (JSON, localStorage on web)
211
+
212
+ ### Polish & Community
213
+ - [ ] Gamepad support
214
+ - [ ] Documentation site (YARD + GitHub Pages)
215
+ - [x] Project generator (`dama new my_game`)
216
+ - [ ] Hot reload
217
+ - [ ] Debug overlay
218
+ - [ ] Publish gem to RubyGems
219
+
220
+ ### Web Integration Testing
221
+ - [ ] Capybara-like + Cuprite browser tests
222
+ - [ ] Game state assertion helpers
223
+ - [ ] CI integration
224
+
225
+ ## License
226
+
227
+ [MIT](LICENSE)
data/dama-logo.svg ADDED
@@ -0,0 +1,91 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="60 -50 500 500" role="img" aria-labelledby="title desc">
2
+ <title id="title">dama-rb logo</title>
3
+ <desc id="desc">A glossy red orb over a circular checkerboard base.</desc>
4
+
5
+ <defs>
6
+ <radialGradient id="orbFill" cx="38%" cy="28%" r="65%">
7
+ <stop offset="0%" stop-color="#ff8d8d"/>
8
+ <stop offset="25%" stop-color="#ff2323"/>
9
+ <stop offset="70%" stop-color="#c30012"/>
10
+ <stop offset="100%" stop-color="#6c0008"/>
11
+ </radialGradient>
12
+
13
+ <radialGradient id="orbGlow" cx="50%" cy="50%" r="50%">
14
+ <stop offset="0%" stop-color="#ff4d4d" stop-opacity="0.55"/>
15
+ <stop offset="100%" stop-color="#ff4d4d" stop-opacity="0"/>
16
+ </radialGradient>
17
+
18
+ <linearGradient id="ringTop" x1="0" y1="0" x2="0" y2="1">
19
+ <stop offset="0%" stop-color="#3d434a"/>
20
+ <stop offset="100%" stop-color="#111418"/>
21
+ </linearGradient>
22
+
23
+ <linearGradient id="ringSide" x1="0" y1="0" x2="0" y2="1">
24
+ <stop offset="0%" stop-color="#20252b"/>
25
+ <stop offset="100%" stop-color="#050607"/>
26
+ </linearGradient>
27
+
28
+ <filter id="softShadow" x="-30%" y="-30%" width="160%" height="160%">
29
+ <feDropShadow dx="0" dy="14" stdDeviation="14" flood-color="#000000" flood-opacity="0.28"/>
30
+ </filter>
31
+
32
+ <clipPath id="iconClip">
33
+ <rect x="60" y="-50" width="500" height="500" rx="90"/>
34
+ </clipPath>
35
+
36
+ <clipPath id="topEllipseClip">
37
+ <ellipse cx="310" cy="250" rx="190" ry="74"/>
38
+ </clipPath>
39
+ </defs>
40
+
41
+ <!-- White rounded-corner background -->
42
+ <rect x="60" y="-50" width="500" height="500" rx="90" fill="#ffffff"/>
43
+
44
+ <g clip-path="url(#iconClip)">
45
+ <g filter="url(#softShadow)">
46
+ <!-- Base side -->
47
+ <ellipse cx="310" cy="286" rx="190" ry="74" fill="url(#ringSide)"/>
48
+
49
+ <!-- Checkerboard top clipped to ellipse -->
50
+ <g clip-path="url(#topEllipseClip)">
51
+ <rect x="120" y="176" width="380" height="148" fill="#f4f4f4"/>
52
+ <g fill="#111111">
53
+ <rect x="120" y="176" width="47.5" height="37"/>
54
+ <rect x="215" y="176" width="47.5" height="37"/>
55
+ <rect x="310" y="176" width="47.5" height="37"/>
56
+ <rect x="405" y="176" width="47.5" height="37"/>
57
+
58
+ <rect x="167.5" y="213" width="47.5" height="37"/>
59
+ <rect x="262.5" y="213" width="47.5" height="37"/>
60
+ <rect x="357.5" y="213" width="47.5" height="37"/>
61
+ <rect x="452.5" y="213" width="47.5" height="37"/>
62
+
63
+ <rect x="120" y="250" width="47.5" height="37"/>
64
+ <rect x="215" y="250" width="47.5" height="37"/>
65
+ <rect x="310" y="250" width="47.5" height="37"/>
66
+ <rect x="405" y="250" width="47.5" height="37"/>
67
+
68
+ <rect x="167.5" y="287" width="47.5" height="37"/>
69
+ <rect x="262.5" y="287" width="47.5" height="37"/>
70
+ <rect x="357.5" y="287" width="47.5" height="37"/>
71
+ <rect x="452.5" y="287" width="47.5" height="37"/>
72
+ </g>
73
+ <ellipse cx="310" cy="250" rx="190" ry="74" fill="url(#ringTop)" opacity="0.16"/>
74
+ </g>
75
+
76
+ <!-- Rim -->
77
+ <ellipse cx="310" cy="250" rx="190" ry="74" fill="none" stroke="#2c3138" stroke-width="16"/>
78
+
79
+ <!-- Orb shadow on board -->
80
+ <ellipse cx="290" cy="250" rx="95" ry="28" fill="#3c0008" opacity="0.28"/>
81
+
82
+ <!-- Orb -->
83
+ <circle cx="255" cy="170" r="118" fill="url(#orbGlow)" opacity="0.9"/>
84
+ <circle cx="255" cy="170" r="102" fill="url(#orbFill)"/>
85
+ <ellipse cx="222" cy="130" rx="32" ry="22" fill="#ffffff" opacity="0.78" transform="rotate(-22 222 130)"/>
86
+ <ellipse cx="294" cy="197" rx="13" ry="10" fill="#ffb2b2" opacity="0.36"/>
87
+ <ellipse cx="322" cy="178" rx="18" ry="14" fill="#ff6b6b" opacity="0.22"/>
88
+ <circle cx="255" cy="170" r="102" fill="none" stroke="#8d000d" stroke-width="4" opacity="0.55"/>
89
+ </g>
90
+ </g>
91
+ </svg>
data/exe/dama ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require "dama"
3
+
4
+ Dama::Cli.run(args: ARGV)
@@ -0,0 +1,66 @@
1
+ module Dama
2
+ # Cycles through sprite sheet frames over time.
3
+ # Supports looping and one-shot animations.
4
+ class Animation
5
+ attr_reader :fps
6
+
7
+ def initialize(frames:, fps:, loop: true, on_complete: nil)
8
+ @frame_indices = frames.to_a
9
+ @fps = fps.to_f
10
+ @looping = loop
11
+ @on_complete = on_complete
12
+ @elapsed = 0.0
13
+ @frame_position = 0
14
+ @completed = false
15
+ end
16
+
17
+ def update(delta_time:)
18
+ return if completed
19
+
20
+ @elapsed += delta_time
21
+ frame_duration = 1.0 / fps
22
+ frames_advanced = (elapsed / frame_duration).to_i
23
+
24
+ return unless frames_advanced > frame_position
25
+
26
+ @frame_position = frames_advanced
27
+ handle_frame_advancement
28
+ end
29
+
30
+ def complete?
31
+ completed
32
+ end
33
+
34
+ def reset
35
+ @elapsed = 0.0
36
+ @frame_position = 0
37
+ @completed = false
38
+ end
39
+
40
+ def current_frame
41
+ frame_indices[frame_position % frame_indices.size]
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :frame_indices, :looping, :on_complete, :elapsed, :frame_position, :completed
47
+
48
+ def handle_frame_advancement
49
+ return if frame_position < frame_indices.size
50
+
51
+ finish_animation
52
+ end
53
+
54
+ def finish_animation
55
+ return loop_animation if looping
56
+
57
+ @frame_position = frame_indices.size - 1
58
+ @completed = true
59
+ on_complete&.call
60
+ end
61
+
62
+ def loop_animation
63
+ @frame_position = frame_position % frame_indices.size
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,56 @@
1
+ module Dama
2
+ # Reference-counted texture cache. Textures are loaded once and shared
3
+ # across all nodes that declare the same path. When the last node using
4
+ # a texture is removed, the texture is unloaded from the GPU.
5
+ class AssetCache
6
+ def initialize(backend:)
7
+ @backend = backend
8
+ @entries = {}
9
+ end
10
+
11
+ # Acquire a texture handle for the given path. Loads from disk on first
12
+ # use; subsequent calls increment the reference count and return the
13
+ # same handle.
14
+ def acquire(path:)
15
+ entry = entries[path]
16
+
17
+ return increment_and_return(entry) if entry
18
+
19
+ handle = backend.load_texture_file(path:)
20
+ entries[path] = { handle:, ref_count: 1 }
21
+ handle
22
+ end
23
+
24
+ # Release one reference to the texture at the given path. When the
25
+ # reference count reaches zero, the texture is unloaded from the GPU.
26
+ def release(path:)
27
+ entry = entries[path]
28
+ return unless entry
29
+
30
+ entry[:ref_count] -= 1
31
+ return unless entry[:ref_count] <= 0
32
+
33
+ backend.unload_texture(handle: entry.fetch(:handle))
34
+ entries.delete(path)
35
+ end
36
+
37
+ # Release all textures regardless of reference count.
38
+ def release_all
39
+ entries.each_value { |entry| backend.unload_texture(handle: entry.fetch(:handle)) }
40
+ entries.clear
41
+ end
42
+
43
+ def handle_for(path:)
44
+ entries.dig(path, :handle)
45
+ end
46
+
47
+ private
48
+
49
+ attr_reader :backend, :entries
50
+
51
+ def increment_and_return(entry)
52
+ entry[:ref_count] += 1
53
+ entry.fetch(:handle)
54
+ end
55
+ end
56
+ end
data/lib/dama/audio.rb ADDED
@@ -0,0 +1,47 @@
1
+ module Dama
2
+ # High-level audio interface for loading and playing sounds.
3
+ # Manages sound handles with reference counting (like AssetCache).
4
+ #
5
+ # Usage in a Node:
6
+ # sound :jump, path: "assets/jump.wav"
7
+ #
8
+ # Usage in update:
9
+ # Audio.play(:jump)
10
+ # Audio.play(:theme, volume: 0.5, loop: true)
11
+ class Audio
12
+ attr_reader :backend
13
+
14
+ def initialize(backend:)
15
+ @backend = backend
16
+ @sounds = {}
17
+ end
18
+
19
+ def load(name:, path:)
20
+ handle = backend.load_sound(path:)
21
+ sounds[name] = handle
22
+ end
23
+
24
+ def play(name, volume: 1.0, loop: false)
25
+ handle = sounds.fetch(name)
26
+ backend.play_sound(handle:, volume:, loop:)
27
+ end
28
+
29
+ def stop_all
30
+ backend.stop_all_sounds
31
+ end
32
+
33
+ def unload(name)
34
+ handle = sounds.delete(name)
35
+ backend.unload_sound(handle:) if handle
36
+ end
37
+
38
+ def unload_all
39
+ sounds.each_value { |handle| backend.unload_sound(handle:) }
40
+ sounds.clear
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :sounds
46
+ end
47
+ end
@@ -0,0 +1,54 @@
1
+ module Dama
2
+ # Discovers and loads all Ruby files in a game project directory.
3
+ # Handles dependency ordering automatically by retrying files
4
+ # that fail due to undefined constants.
5
+ class AutoLoader
6
+ MAX_PASSES = 10
7
+
8
+ def initialize(game_dir:)
9
+ @game_dir = game_dir
10
+ end
11
+
12
+ def load_all
13
+ files = discover_files
14
+ load_with_retries(files:)
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :game_dir
20
+
21
+ def discover_files
22
+ Dir[File.join(game_dir, "**", "*.rb")]
23
+ end
24
+
25
+ # Repeatedly attempt to load files, retrying those that fail
26
+ # with NameError (undefined constant — dependency not yet loaded).
27
+ # Stops when all files are loaded or no progress is made.
28
+ def load_with_retries(files:)
29
+ remaining = files.dup
30
+
31
+ MAX_PASSES.times do
32
+ failed = []
33
+
34
+ remaining.each do |file|
35
+ require file
36
+ rescue NameError
37
+ failed << file
38
+ end
39
+
40
+ return if failed.empty?
41
+
42
+ # No progress — the remaining files have unresolvable errors.
43
+ raise_load_error(failed:) if failed.size == remaining.size
44
+
45
+ remaining = failed
46
+ end
47
+ end
48
+
49
+ def raise_load_error(failed:)
50
+ # Try loading each failed file one more time to get the real error message.
51
+ failed.each { |file| require file }
52
+ end
53
+ end
54
+ end