@bornengine/engine 0.4.16

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 (213) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +231 -0
  3. package/native/android/Cargo.lock +1848 -0
  4. package/native/android/Cargo.toml +24 -0
  5. package/native/android/src/lib.rs +702 -0
  6. package/native/ios/Cargo.lock +1690 -0
  7. package/native/ios/Cargo.toml +32 -0
  8. package/native/ios/src/lib.rs +1267 -0
  9. package/native/linux/Cargo.lock +3279 -0
  10. package/native/linux/Cargo.toml +29 -0
  11. package/native/linux/src/lib.rs +1331 -0
  12. package/native/macos/Cargo.lock +3310 -0
  13. package/native/macos/Cargo.toml +46 -0
  14. package/native/macos/src/lib.rs +1302 -0
  15. package/native/shared/Cargo.lock +1899 -0
  16. package/native/shared/Cargo.toml +62 -0
  17. package/native/shared/assets/default_font.ttf +0 -0
  18. package/native/shared/build.rs +270 -0
  19. package/native/shared/shaders/common/clouds.wgsl +122 -0
  20. package/native/shared/shaders/common/fog.wgsl +16 -0
  21. package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
  22. package/native/shared/shaders/common/imposter.wgsl +112 -0
  23. package/native/shared/shaders/common/pbr.wgsl +186 -0
  24. package/native/shared/shaders/common/shadows.wgsl +186 -0
  25. package/native/shared/shaders/common/sky.wgsl +8 -0
  26. package/native/shared/shaders/common/tonemap.wgsl +25 -0
  27. package/native/shared/shaders/impulse_field.wgsl +57 -0
  28. package/native/shared/shaders/material_abi.wgsl +383 -0
  29. package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
  30. package/native/shared/src/anim_mixer.rs +61 -0
  31. package/native/shared/src/attach.rs +263 -0
  32. package/native/shared/src/audio/decode.rs +123 -0
  33. package/native/shared/src/audio/mod.rs +863 -0
  34. package/native/shared/src/audio/render.rs +892 -0
  35. package/native/shared/src/audio/spsc.rs +156 -0
  36. package/native/shared/src/audio/stream.rs +226 -0
  37. package/native/shared/src/custom_shaders.rs +104 -0
  38. package/native/shared/src/decals.rs +245 -0
  39. package/native/shared/src/drs.rs +211 -0
  40. package/native/shared/src/engine.rs +261 -0
  41. package/native/shared/src/ffi.rs +116 -0
  42. package/native/shared/src/ffi_core/assets.rs +388 -0
  43. package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
  44. package/native/shared/src/ffi_core/draw.rs +334 -0
  45. package/native/shared/src/ffi_core/game_loop.rs +577 -0
  46. package/native/shared/src/ffi_core/input.rs +234 -0
  47. package/native/shared/src/ffi_core/mod.rs +127 -0
  48. package/native/shared/src/ffi_core/models.rs +1154 -0
  49. package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
  50. package/native/shared/src/ffi_core/scene.rs +626 -0
  51. package/native/shared/src/ffi_core/vfx.rs +212 -0
  52. package/native/shared/src/ffi_core/visual.rs +691 -0
  53. package/native/shared/src/frame_callbacks.rs +122 -0
  54. package/native/shared/src/geometry.rs +236 -0
  55. package/native/shared/src/handles.rs +182 -0
  56. package/native/shared/src/input.rs +448 -0
  57. package/native/shared/src/jolt_sys.rs +822 -0
  58. package/native/shared/src/lib.rs +55 -0
  59. package/native/shared/src/models.rs +1093 -0
  60. package/native/shared/src/models_gltf.rs +1280 -0
  61. package/native/shared/src/particles.rs +391 -0
  62. package/native/shared/src/physics_jolt.rs +1908 -0
  63. package/native/shared/src/picking.rs +298 -0
  64. package/native/shared/src/postfx.rs +345 -0
  65. package/native/shared/src/profiler.rs +492 -0
  66. package/native/shared/src/ragdoll.rs +474 -0
  67. package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
  68. package/native/shared/src/renderer/brdf_lut.rs +154 -0
  69. package/native/shared/src/renderer/draw2d.rs +143 -0
  70. package/native/shared/src/renderer/formats.rs +822 -0
  71. package/native/shared/src/renderer/froxel.rs +421 -0
  72. package/native/shared/src/renderer/gi_bake.rs +653 -0
  73. package/native/shared/src/renderer/graph.rs +462 -0
  74. package/native/shared/src/renderer/hiz.rs +269 -0
  75. package/native/shared/src/renderer/hot_reload.rs +390 -0
  76. package/native/shared/src/renderer/impulse_field.rs +456 -0
  77. package/native/shared/src/renderer/lighting.rs +154 -0
  78. package/native/shared/src/renderer/material_instancing.rs +171 -0
  79. package/native/shared/src/renderer/material_pipeline.rs +700 -0
  80. package/native/shared/src/renderer/material_system.rs +1996 -0
  81. package/native/shared/src/renderer/material_system_tests.rs +601 -0
  82. package/native/shared/src/renderer/material_system_wasm.rs +41 -0
  83. package/native/shared/src/renderer/mod.rs +12556 -0
  84. package/native/shared/src/renderer/model_draw.rs +641 -0
  85. package/native/shared/src/renderer/occlusion.rs +429 -0
  86. package/native/shared/src/renderer/planar_pass.rs +593 -0
  87. package/native/shared/src/renderer/planar_reflection.rs +499 -0
  88. package/native/shared/src/renderer/post_pass.rs +249 -0
  89. package/native/shared/src/renderer/postfx_chain.rs +728 -0
  90. package/native/shared/src/renderer/pt_pass.rs +577 -0
  91. package/native/shared/src/renderer/scene_pass.rs +607 -0
  92. package/native/shared/src/renderer/shader_include.rs +205 -0
  93. package/native/shared/src/renderer/shader_library.rs +135 -0
  94. package/native/shared/src/renderer/shaders/ao.rs +570 -0
  95. package/native/shared/src/renderer/shaders/core.rs +1243 -0
  96. package/native/shared/src/renderer/shaders/env.rs +907 -0
  97. package/native/shared/src/renderer/shaders/gi.rs +810 -0
  98. package/native/shared/src/renderer/shaders/mod.rs +19 -0
  99. package/native/shared/src/renderer/shaders/post.rs +1558 -0
  100. package/native/shared/src/renderer/shaders/pt.rs +1859 -0
  101. package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
  102. package/native/shared/src/renderer/shadow_pass.rs +731 -0
  103. package/native/shared/src/renderer/ssgi_pass.rs +392 -0
  104. package/native/shared/src/renderer/ssr_pass.rs +188 -0
  105. package/native/shared/src/renderer/texture_store.rs +473 -0
  106. package/native/shared/src/renderer/transient.rs +591 -0
  107. package/native/shared/src/renderer/types.rs +941 -0
  108. package/native/shared/src/renderer/util.rs +152 -0
  109. package/native/shared/src/scene.rs +1362 -0
  110. package/native/shared/src/sdf_cache.rs +274 -0
  111. package/native/shared/src/shadows.rs +1036 -0
  112. package/native/shared/src/staging.rs +102 -0
  113. package/native/shared/src/string_header.rs +266 -0
  114. package/native/shared/src/text_renderer.rs +502 -0
  115. package/native/shared/src/textures.rs +197 -0
  116. package/native/tvos/Cargo.lock +1693 -0
  117. package/native/tvos/Cargo.toml +36 -0
  118. package/native/tvos/metal-patched/Cargo.toml +178 -0
  119. package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
  120. package/native/tvos/metal-patched/LICENSE-MIT +25 -0
  121. package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
  122. package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
  123. package/native/tvos/metal-patched/src/argument.rs +366 -0
  124. package/native/tvos/metal-patched/src/blitpass.rs +102 -0
  125. package/native/tvos/metal-patched/src/buffer.rs +71 -0
  126. package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
  127. package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
  128. package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
  129. package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
  130. package/native/tvos/metal-patched/src/computepass.rs +107 -0
  131. package/native/tvos/metal-patched/src/constants.rs +152 -0
  132. package/native/tvos/metal-patched/src/counters.rs +119 -0
  133. package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
  134. package/native/tvos/metal-patched/src/device.rs +2134 -0
  135. package/native/tvos/metal-patched/src/drawable.rs +39 -0
  136. package/native/tvos/metal-patched/src/encoder.rs +2041 -0
  137. package/native/tvos/metal-patched/src/heap.rs +281 -0
  138. package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
  139. package/native/tvos/metal-patched/src/lib.rs +657 -0
  140. package/native/tvos/metal-patched/src/library.rs +902 -0
  141. package/native/tvos/metal-patched/src/mps.rs +575 -0
  142. package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
  143. package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
  144. package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
  145. package/native/tvos/metal-patched/src/renderpass.rs +443 -0
  146. package/native/tvos/metal-patched/src/resource.rs +182 -0
  147. package/native/tvos/metal-patched/src/sampler.rs +165 -0
  148. package/native/tvos/metal-patched/src/sync.rs +178 -0
  149. package/native/tvos/metal-patched/src/texture.rs +352 -0
  150. package/native/tvos/metal-patched/src/types.rs +90 -0
  151. package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
  152. package/native/tvos/src/audio_backend.rs +197 -0
  153. package/native/tvos/src/lib.rs +1891 -0
  154. package/native/visionos/Cargo.lock +1693 -0
  155. package/native/visionos/Cargo.toml +40 -0
  156. package/native/visionos/src/audio_backend.rs +197 -0
  157. package/native/visionos/src/lib.rs +1887 -0
  158. package/native/watchos/Cargo.lock +16 -0
  159. package/native/watchos/Cargo.toml +19 -0
  160. package/native/watchos/shaders/bloom_postfx.metal +99 -0
  161. package/native/watchos/src/BloomWatchApp.swift +1267 -0
  162. package/native/watchos/src/BloomWatchAudio.swift +179 -0
  163. package/native/watchos/src/audio.rs +55 -0
  164. package/native/watchos/src/draw_list.rs +229 -0
  165. package/native/watchos/src/ffi_stubs.rs +915 -0
  166. package/native/watchos/src/ffi_stubs_manual.rs +35 -0
  167. package/native/watchos/src/lib.rs +1124 -0
  168. package/native/watchos/src/models.rs +746 -0
  169. package/native/watchos/src/postfx.rs +95 -0
  170. package/native/watchos/src/scene.rs +534 -0
  171. package/native/watchos/src/textures.rs +184 -0
  172. package/native/web/Cargo.lock +1657 -0
  173. package/native/web/Cargo.toml +43 -0
  174. package/native/web/bloom_glue.js +695 -0
  175. package/native/web/build.sh +131 -0
  176. package/native/web/index.html +35 -0
  177. package/native/web/jolt_bridge.js +1519 -0
  178. package/native/web/src/input_ffi.rs +286 -0
  179. package/native/web/src/lib.rs +1796 -0
  180. package/native/web/src/material_ffi.rs +710 -0
  181. package/native/web/src/parity_ffi.rs +343 -0
  182. package/native/web/src/physics_ffi.rs +643 -0
  183. package/native/web/src/ragdoll_ffi.rs +250 -0
  184. package/native/web/src/render_settings.rs +98 -0
  185. package/native/windows/Cargo.lock +1815 -0
  186. package/native/windows/Cargo.toml +68 -0
  187. package/native/windows/src/lib.rs +1486 -0
  188. package/package.json +4279 -0
  189. package/src/audio/index.ts +315 -0
  190. package/src/core/colors.ts +63 -0
  191. package/src/core/index.ts +1206 -0
  192. package/src/core/keys.ts +63 -0
  193. package/src/core/types.ts +104 -0
  194. package/src/index.ts +171 -0
  195. package/src/math/index.ts +516 -0
  196. package/src/mobile/index.ts +294 -0
  197. package/src/models/index.ts +1258 -0
  198. package/src/physics/index.ts +1134 -0
  199. package/src/scene/index.ts +698 -0
  200. package/src/shapes/index.ts +120 -0
  201. package/src/text/index.ts +48 -0
  202. package/src/textures/index.ts +187 -0
  203. package/src/vfx/index.ts +191 -0
  204. package/src/world/index.ts +24 -0
  205. package/src/world/loader.ts +423 -0
  206. package/src/world/prefab.ts +217 -0
  207. package/src/world/render.ts +172 -0
  208. package/src/world/saver.ts +108 -0
  209. package/src/world/serialize.ts +301 -0
  210. package/src/world/terrain.ts +355 -0
  211. package/src/world/types.ts +160 -0
  212. package/src/world/validate.ts +319 -0
  213. package/src/world/version.ts +114 -0
@@ -0,0 +1,179 @@
1
+ // BloomWatchAudio — AVAudioPlayer-backed implementation of bloom's audio FFI.
2
+ //
3
+ // The Rust side of bloom-watchos calls into these C-exported functions via
4
+ // extern "C" so sound/music paths stay string-based at the Perry/Rust boundary
5
+ // but decoding, decoding, and playback happen on the Swift side where we
6
+ // have AVAudioPlayer (and, on watchOS, no obvious Rust-native audio path).
7
+ //
8
+ // Handles are 1-based to match the rest of bloom-watchos's texture/font
9
+ // registries — 0 signals "none / not loaded".
10
+
11
+ import Foundation
12
+ import AVFoundation
13
+
14
+ // MARK: - Registry
15
+
16
+ final class BloomAudioManager {
17
+ static let shared = BloomAudioManager()
18
+
19
+ private var sounds: [AVAudioPlayer] = []
20
+ private var musics: [AVAudioPlayer] = []
21
+ private var masterVolume: Float = 1.0
22
+ private let lock = NSLock()
23
+ private var sessionStarted = false
24
+
25
+ func initAudio() {
26
+ guard !sessionStarted else { return }
27
+ sessionStarted = true
28
+ // AVAudioSession on watchOS requires explicit activation. Ambient
29
+ // category = play alongside other audio without interruption; good
30
+ // default for games.
31
+ let session = AVAudioSession.sharedInstance()
32
+ try? session.setCategory(.ambient, mode: .default, options: [.mixWithOthers])
33
+ try? session.setActive(true, options: [])
34
+ }
35
+
36
+ func closeAudio() {
37
+ lock.lock()
38
+ sounds.forEach { $0.stop() }
39
+ musics.forEach { $0.stop() }
40
+ sounds.removeAll()
41
+ musics.removeAll()
42
+ lock.unlock()
43
+ }
44
+
45
+ /// Load a sound from a filesystem path. Returns a 1-based handle or 0 on
46
+ /// failure. The path is expected to be already bundle-resolved on the
47
+ /// Rust side (textures::resolve_bundle_path).
48
+ func loadSound(path: String) -> UInt32 {
49
+ guard let player = try? AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) else {
50
+ return 0
51
+ }
52
+ player.prepareToPlay()
53
+ lock.lock()
54
+ sounds.append(player)
55
+ let handle = UInt32(sounds.count)
56
+ lock.unlock()
57
+ return handle
58
+ }
59
+
60
+ func playSound(_ handle: UInt32) {
61
+ guard let p = sound(handle) else { return }
62
+ p.volume = masterVolume
63
+ p.currentTime = 0
64
+ p.play()
65
+ }
66
+
67
+ func stopSound(_ handle: UInt32) {
68
+ sound(handle)?.stop()
69
+ }
70
+
71
+ func setSoundVolume(_ handle: UInt32, _ v: Float) {
72
+ sound(handle)?.volume = v * masterVolume
73
+ }
74
+
75
+ func setMasterVolume(_ v: Float) {
76
+ lock.lock()
77
+ masterVolume = v
78
+ sounds.forEach { $0.volume = v }
79
+ musics.forEach { $0.volume = v }
80
+ lock.unlock()
81
+ }
82
+
83
+ func loadMusic(path: String) -> UInt32 {
84
+ guard let player = try? AVAudioPlayer(contentsOf: URL(fileURLWithPath: path)) else {
85
+ return 0
86
+ }
87
+ player.numberOfLoops = -1 // loop forever
88
+ player.prepareToPlay()
89
+ lock.lock()
90
+ musics.append(player)
91
+ let handle = UInt32(musics.count)
92
+ lock.unlock()
93
+ return handle
94
+ }
95
+
96
+ func playMusic(_ handle: UInt32) {
97
+ guard let p = music(handle) else { return }
98
+ p.volume = masterVolume
99
+ p.play()
100
+ }
101
+
102
+ func stopMusic(_ handle: UInt32) { music(handle)?.stop() }
103
+ func setMusicVolume(_ handle: UInt32, _ v: Float) { music(handle)?.volume = v * masterVolume }
104
+ func isMusicPlaying(_ handle: UInt32) -> Bool { music(handle)?.isPlaying ?? false }
105
+
106
+ private func sound(_ handle: UInt32) -> AVAudioPlayer? {
107
+ lock.lock(); defer { lock.unlock() }
108
+ let i = Int(handle)
109
+ return (i >= 1 && i <= sounds.count) ? sounds[i - 1] : nil
110
+ }
111
+
112
+ private func music(_ handle: UInt32) -> AVAudioPlayer? {
113
+ lock.lock(); defer { lock.unlock() }
114
+ let i = Int(handle)
115
+ return (i >= 1 && i <= musics.count) ? musics[i - 1] : nil
116
+ }
117
+ }
118
+
119
+ // MARK: - C-ABI exports (called from bloom-watchos Rust crate)
120
+
121
+ @_cdecl("bloom_watchos_audio_init")
122
+ public func bloom_watchos_audio_init() {
123
+ BloomAudioManager.shared.initAudio()
124
+ }
125
+
126
+ @_cdecl("bloom_watchos_audio_close")
127
+ public func bloom_watchos_audio_close() {
128
+ BloomAudioManager.shared.closeAudio()
129
+ }
130
+
131
+ @_cdecl("bloom_watchos_sound_load")
132
+ public func bloom_watchos_sound_load(_ path: UnsafePointer<CChar>) -> UInt32 {
133
+ BloomAudioManager.shared.loadSound(path: String(cString: path))
134
+ }
135
+
136
+ @_cdecl("bloom_watchos_sound_play")
137
+ public func bloom_watchos_sound_play(_ handle: UInt32) {
138
+ BloomAudioManager.shared.playSound(handle)
139
+ }
140
+
141
+ @_cdecl("bloom_watchos_sound_stop")
142
+ public func bloom_watchos_sound_stop(_ handle: UInt32) {
143
+ BloomAudioManager.shared.stopSound(handle)
144
+ }
145
+
146
+ @_cdecl("bloom_watchos_sound_volume")
147
+ public func bloom_watchos_sound_volume(_ handle: UInt32, _ v: Float) {
148
+ BloomAudioManager.shared.setSoundVolume(handle, v)
149
+ }
150
+
151
+ @_cdecl("bloom_watchos_master_volume")
152
+ public func bloom_watchos_master_volume(_ v: Float) {
153
+ BloomAudioManager.shared.setMasterVolume(v)
154
+ }
155
+
156
+ @_cdecl("bloom_watchos_music_load")
157
+ public func bloom_watchos_music_load(_ path: UnsafePointer<CChar>) -> UInt32 {
158
+ BloomAudioManager.shared.loadMusic(path: String(cString: path))
159
+ }
160
+
161
+ @_cdecl("bloom_watchos_music_play")
162
+ public func bloom_watchos_music_play(_ handle: UInt32) {
163
+ BloomAudioManager.shared.playMusic(handle)
164
+ }
165
+
166
+ @_cdecl("bloom_watchos_music_stop")
167
+ public func bloom_watchos_music_stop(_ handle: UInt32) {
168
+ BloomAudioManager.shared.stopMusic(handle)
169
+ }
170
+
171
+ @_cdecl("bloom_watchos_music_volume")
172
+ public func bloom_watchos_music_volume(_ handle: UInt32, _ v: Float) {
173
+ BloomAudioManager.shared.setMusicVolume(handle, v)
174
+ }
175
+
176
+ @_cdecl("bloom_watchos_music_is_playing")
177
+ public func bloom_watchos_music_is_playing(_ handle: UInt32) -> UInt32 {
178
+ BloomAudioManager.shared.isMusicPlaying(handle) ? 1 : 0
179
+ }
@@ -0,0 +1,55 @@
1
+ //! Rust-side audio FFI that forwards to the Swift `BloomAudioManager`.
2
+ //!
3
+ //! The Swift shell owns AVAudioPlayer + AVAudioSession; this module is a
4
+ //! thin pass-through that decodes Perry string args + resolves bundle-relative
5
+ //! paths the same way textures does.
6
+
7
+ use std::ffi::CString;
8
+ use std::os::raw::c_char;
9
+
10
+ use crate::textures;
11
+
12
+ extern "C" {
13
+ fn bloom_watchos_audio_init();
14
+ fn bloom_watchos_audio_close();
15
+ fn bloom_watchos_sound_load(path: *const c_char) -> u32;
16
+ fn bloom_watchos_sound_play(handle: u32);
17
+ fn bloom_watchos_sound_stop(handle: u32);
18
+ fn bloom_watchos_sound_volume(handle: u32, volume: f32);
19
+ fn bloom_watchos_master_volume(volume: f32);
20
+ fn bloom_watchos_music_load(path: *const c_char) -> u32;
21
+ fn bloom_watchos_music_play(handle: u32);
22
+ fn bloom_watchos_music_stop(handle: u32);
23
+ fn bloom_watchos_music_volume(handle: u32, volume: f32);
24
+ fn bloom_watchos_music_is_playing(handle: u32) -> u32;
25
+ }
26
+
27
+ fn load_with<F>(path: &str, load_fn: F) -> u32
28
+ where F: FnOnce(*const c_char) -> u32
29
+ {
30
+ if path.is_empty() { return 0; }
31
+ let full = if path.starts_with('/') { path.to_string() } else { textures::resolve_bundle_path(path) };
32
+ let Ok(c) = CString::new(full) else { return 0 };
33
+ unsafe { load_fn(c.as_ptr()) }
34
+ }
35
+
36
+ pub fn init_audio() { unsafe { bloom_watchos_audio_init(); } }
37
+ pub fn close_audio() { unsafe { bloom_watchos_audio_close(); } }
38
+
39
+ pub fn load_sound(path: &str) -> u32 {
40
+ load_with(path, |p| unsafe { bloom_watchos_sound_load(p) })
41
+ }
42
+ pub fn play_sound(handle: u32) { unsafe { bloom_watchos_sound_play(handle); } }
43
+ pub fn stop_sound(handle: u32) { unsafe { bloom_watchos_sound_stop(handle); } }
44
+ pub fn set_sound_volume(handle: u32, v: f32) { unsafe { bloom_watchos_sound_volume(handle, v); } }
45
+ pub fn set_master_volume(v: f32) { unsafe { bloom_watchos_master_volume(v); } }
46
+
47
+ pub fn load_music(path: &str) -> u32 {
48
+ load_with(path, |p| unsafe { bloom_watchos_music_load(p) })
49
+ }
50
+ pub fn play_music(handle: u32) { unsafe { bloom_watchos_music_play(handle); } }
51
+ pub fn stop_music(handle: u32) { unsafe { bloom_watchos_music_stop(handle); } }
52
+ pub fn set_music_volume(handle: u32, v: f32) { unsafe { bloom_watchos_music_volume(handle, v); } }
53
+ pub fn is_music_playing(handle: u32) -> bool {
54
+ unsafe { bloom_watchos_music_is_playing(handle) != 0 }
55
+ }
@@ -0,0 +1,229 @@
1
+ //! Thread-safe draw-command list the Swift Canvas reads each frame.
2
+ //!
3
+ //! Game thread (spawned by the Swift shell calling `_perry_user_main`) pushes
4
+ //! commands via `push()` between `begin()` / `end()`. At `end()`, the frame
5
+ //! counter increments. Swift side calls `snapshot()` to atomically copy the
6
+ //! current commands into a static buffer it can then iterate via `cmd_at()`
7
+ //! without holding any lock. The snapshot is valid until the next `snapshot()`
8
+ //! call — Swift drains it synchronously inside a single Canvas closure.
9
+
10
+ use std::sync::atomic::{AtomicU64, Ordering};
11
+ use std::sync::Mutex;
12
+
13
+ /// Inline text capacity — long enough for HUD / menu strings; games needing
14
+ /// longer text should split across commands.
15
+ pub const TEXT_CAP: usize = 256;
16
+
17
+ /// Command kinds. Kept stable across Swift + Rust — don't renumber without
18
+ /// updating BloomWatchApp.swift.
19
+ #[allow(dead_code)]
20
+ pub mod kind {
21
+ pub const CLEAR: i32 = 0;
22
+ pub const RECT: i32 = 1;
23
+ pub const RECT_LINES: i32 = 2;
24
+ pub const CIRCLE: i32 = 3;
25
+ pub const CIRCLE_LINES: i32 = 4;
26
+ pub const LINE: i32 = 5;
27
+ pub const TRIANGLE: i32 = 6;
28
+ pub const TEXTURE: i32 = 7; // whole texture at (x,y)
29
+ pub const TEXTURE_REC: i32 = 8; // source rect → dest rect, no rotation
30
+ pub const TEXTURE_PRO: i32 = 9; // full: source, dest, origin, rotation
31
+ pub const TEXT: i32 = 10;
32
+
33
+ // 2D camera mode markers. BEGIN carries the camera in reused fields:
34
+ // x,y = screen offset; w,h = world target; size = zoom. The Swift Canvas
35
+ // applies the matching affine transform to every command until END.
36
+ pub const BEGIN_2D: i32 = 11;
37
+ pub const END_2D: i32 = 12;
38
+
39
+ // 3D immediate-mode primitives (pos = x,y,z; w,h = scale; src_x,y,z = secondary).
40
+ pub const CUBE: i32 = 20; // pos (x,y,z), size (w,h,size=depth)
41
+ pub const CUBE_WIRES: i32 = 21;
42
+ pub const SPHERE: i32 = 22; // pos (x,y,z), radius (w)
43
+ pub const SPHERE_WIRES: i32 = 23;
44
+ pub const CYLINDER: i32 = 24; // pos (x,y,z), radius (w), height (h)
45
+ pub const PLANE: i32 = 25; // pos (x,y,z), size (w,h)
46
+ pub const GRID: i32 = 26; // slices (w), spacing (h)
47
+ }
48
+
49
+ #[repr(C)]
50
+ #[derive(Clone, Copy)]
51
+ pub struct DrawCmd {
52
+ pub kind: i32,
53
+ pub _pad0: i32,
54
+ pub tex: u32,
55
+ pub _pad1: u32,
56
+
57
+ // Destination geometry.
58
+ pub x: f64,
59
+ pub y: f64,
60
+ pub w: f64,
61
+ pub h: f64,
62
+
63
+ // Source sub-rectangle (textures) or secondary point (lines).
64
+ pub src_x: f64,
65
+ pub src_y: f64,
66
+ pub src_w: f64,
67
+ pub src_h: f64,
68
+
69
+ // Origin / pivot for rotation + scale (relative to dest).
70
+ pub ox: f64,
71
+ pub oy: f64,
72
+
73
+ // Color, 0-255 per channel.
74
+ pub r: f64,
75
+ pub g: f64,
76
+ pub b: f64,
77
+ pub a: f64,
78
+
79
+ // rotation (degrees), size (font), thickness (lines), pad.
80
+ pub rot: f64,
81
+ pub size: f64,
82
+ pub thickness: f64,
83
+ pub _pad2: f64,
84
+
85
+ // Inline UTF-8 for text commands; text_len == 0 otherwise.
86
+ pub text: [u8; TEXT_CAP],
87
+ pub text_len: u64,
88
+ }
89
+
90
+ impl DrawCmd {
91
+ pub const fn zero() -> Self {
92
+ Self {
93
+ kind: 0,
94
+ _pad0: 0,
95
+ tex: 0,
96
+ _pad1: 0,
97
+ x: 0.0, y: 0.0, w: 0.0, h: 0.0,
98
+ src_x: 0.0, src_y: 0.0, src_w: 0.0, src_h: 0.0,
99
+ ox: 0.0, oy: 0.0,
100
+ r: 255.0, g: 255.0, b: 255.0, a: 255.0,
101
+ rot: 0.0, size: 0.0, thickness: 1.0, _pad2: 0.0,
102
+ text: [0; TEXT_CAP],
103
+ text_len: 0,
104
+ }
105
+ }
106
+
107
+ pub fn set_text(&mut self, s: &str) {
108
+ let bytes = s.as_bytes();
109
+ let n = bytes.len().min(TEXT_CAP);
110
+ self.text[..n].copy_from_slice(&bytes[..n]);
111
+ self.text_len = n as u64;
112
+ }
113
+ }
114
+
115
+ struct Inner {
116
+ // The "building" list — game thread pushes into this between begin/end.
117
+ building: Vec<DrawCmd>,
118
+ // The "ready" list — snapshot target, what Swift reads.
119
+ ready: Vec<DrawCmd>,
120
+ }
121
+
122
+ static INNER: Mutex<Inner> = Mutex::new(Inner {
123
+ building: Vec::new(),
124
+ ready: Vec::new(),
125
+ });
126
+
127
+ /// Monotonic frame counter — Swift polls this and invalidates Canvas when it
128
+ /// changes. Game thread bumps it on `end()`.
129
+ static FRAME: AtomicU64 = AtomicU64::new(0);
130
+
131
+ /// Clear color packed as f64 bits for the current frame (r,g,b,a).
132
+ static CLEAR: [AtomicU64; 4] = [
133
+ AtomicU64::new(0),
134
+ AtomicU64::new(0),
135
+ AtomicU64::new(0),
136
+ AtomicU64::new(255f64.to_bits()),
137
+ ];
138
+
139
+ /// 3D camera state — pos (xyz), target (xyz), up (xyz), fovy, proj (0=ortho, 1=persp).
140
+ /// Written by bloom_begin_mode_3d, read once per frame by the Swift SceneView.
141
+ static CAMERA: [AtomicU64; 11] = {
142
+ const INIT: AtomicU64 = AtomicU64::new(0);
143
+ [INIT; 11]
144
+ };
145
+ /// Non-zero when a 3D section has ever been opened — tells the Swift shell
146
+ /// it needs to host a SceneView, not just a Canvas.
147
+ static HAS_3D: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
148
+
149
+ pub fn begin() {
150
+ let mut g = INNER.lock().unwrap();
151
+ g.building.clear();
152
+ }
153
+
154
+ pub fn end() {
155
+ {
156
+ let mut g = INNER.lock().unwrap();
157
+ // Move building → ready via take; clear the building list for the
158
+ // next frame. Can't use mem::swap on two fields of the same borrow.
159
+ let done = std::mem::take(&mut g.building);
160
+ g.ready = done;
161
+ }
162
+ FRAME.fetch_add(1, Ordering::Release);
163
+ }
164
+
165
+ pub fn push(cmd: DrawCmd) {
166
+ let mut g = INNER.lock().unwrap();
167
+ g.building.push(cmd);
168
+ }
169
+
170
+ pub fn set_clear(r: f64, g: f64, b: f64, a: f64) {
171
+ CLEAR[0].store(r.to_bits(), Ordering::Relaxed);
172
+ CLEAR[1].store(g.to_bits(), Ordering::Relaxed);
173
+ CLEAR[2].store(b.to_bits(), Ordering::Relaxed);
174
+ CLEAR[3].store(a.to_bits(), Ordering::Relaxed);
175
+ }
176
+
177
+ pub fn clear_rgba() -> [f64; 4] {
178
+ [
179
+ f64::from_bits(CLEAR[0].load(Ordering::Relaxed)),
180
+ f64::from_bits(CLEAR[1].load(Ordering::Relaxed)),
181
+ f64::from_bits(CLEAR[2].load(Ordering::Relaxed)),
182
+ f64::from_bits(CLEAR[3].load(Ordering::Relaxed)),
183
+ ]
184
+ }
185
+
186
+ pub fn frame() -> u64 {
187
+ FRAME.load(Ordering::Acquire)
188
+ }
189
+
190
+ pub fn set_camera(px: f64, py: f64, pz: f64,
191
+ tx: f64, ty: f64, tz: f64,
192
+ ux: f64, uy: f64, uz: f64,
193
+ fovy: f64, proj: f64) {
194
+ let vals = [px, py, pz, tx, ty, tz, ux, uy, uz, fovy, proj];
195
+ for (i, v) in vals.iter().enumerate() {
196
+ CAMERA[i].store(v.to_bits(), Ordering::Relaxed);
197
+ }
198
+ HAS_3D.store(true, std::sync::atomic::Ordering::Release);
199
+ }
200
+
201
+ pub fn camera_snapshot(out: *mut f64) {
202
+ if out.is_null() { return; }
203
+ unsafe {
204
+ for i in 0..11 {
205
+ *out.add(i) = f64::from_bits(CAMERA[i].load(Ordering::Relaxed));
206
+ }
207
+ }
208
+ }
209
+
210
+ pub fn has_3d() -> bool {
211
+ HAS_3D.load(std::sync::atomic::Ordering::Acquire)
212
+ }
213
+
214
+ /// Bulk-copy the ready list into a Swift-supplied buffer. Returns the number
215
+ /// of commands actually copied (never more than `max`). Swift allocates a
216
+ /// single buffer once and reuses it each frame.
217
+ pub fn copy_into(dst: *mut DrawCmd, max: i64) -> i64 {
218
+ if dst.is_null() || max <= 0 {
219
+ return 0;
220
+ }
221
+ let g = INNER.lock().unwrap();
222
+ let n = g.ready.len().min(max as usize);
223
+ // SAFETY: caller guarantees `dst` points to at least `max` DrawCmds of
224
+ // writable memory with the same #[repr(C)] layout.
225
+ unsafe {
226
+ std::ptr::copy_nonoverlapping(g.ready.as_ptr(), dst, n);
227
+ }
228
+ n as i64
229
+ }