@bloomengine/engine 0.4.9 → 0.4.11

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.
@@ -1393,6 +1393,10 @@ pub extern "C" fn bloom_inject_gamepad_button_up(button: f64) {
1393
1393
  }
1394
1394
  #[no_mangle]
1395
1395
  pub extern "C" fn bloom_get_platform() -> f64 { 5.0 }
1396
+
1397
+ /// Preferred OS language packed as `c0*256+c1`. TODO: real per-OS detection; returns "en" for now.
1398
+ #[no_mangle]
1399
+ pub extern "C" fn bloom_get_language() -> f64 { 25966.0 }
1396
1400
  #[no_mangle]
1397
1401
  pub extern "C" fn bloom_is_any_input_pressed() -> f64 {
1398
1402
  if engine().input.is_any_input_pressed() { 1.0 } else { 0.0 }
@@ -23,6 +23,6 @@ panic = "abort"
23
23
  image = { version = "0.25", default-features = false, features = ["hdr"] }
24
24
  bloom-shared = { path = "../shared", default-features = false, features = ["mp3"] }
25
25
  objc2 = "0.6"
26
- objc2-foundation = { version = "0.3", features = ["NSDate", "NSRunLoop", "NSString", "NSThread", "NSObject"] }
26
+ objc2-foundation = { version = "0.3", features = ["NSDate", "NSRunLoop", "NSString", "NSThread", "NSObject", "NSLocale", "NSArray"] }
27
27
  raw-window-handle = "0.6"
28
28
  wgpu = "29"
@@ -2078,6 +2078,14 @@ pub extern "C" fn bloom_inject_gamepad_button_up(button: f64) {
2078
2078
  }
2079
2079
  #[no_mangle]
2080
2080
  pub extern "C" fn bloom_get_platform() -> f64 { 2.0 }
2081
+
2082
+ /// Preferred OS language packed as `c0*256+c1` (ISO-639 primary subtag). See macos lib for format.
2083
+ #[no_mangle]
2084
+ pub extern "C" fn bloom_get_language() -> f64 {
2085
+ fn pack(code: &str) -> f64 { let l = code.to_ascii_lowercase(); let b = l.as_bytes(); if b.len() >= 2 { (b[0] as f64) * 256.0 + (b[1] as f64) } else { 25966.0 } }
2086
+ let langs = objc2_foundation::NSLocale::preferredLanguages();
2087
+ match langs.firstObject() { Some(s) => pack(&s.to_string()), None => 25966.0 }
2088
+ }
2081
2089
  #[no_mangle]
2082
2090
  pub extern "C" fn bloom_is_any_input_pressed() -> f64 {
2083
2091
  if engine().input.is_any_input_pressed() { 1.0 } else { 0.0 }
@@ -1846,6 +1846,14 @@ pub extern "C" fn bloom_inject_gamepad_button_up(button: f64) {
1846
1846
  }
1847
1847
  #[no_mangle]
1848
1848
  pub extern "C" fn bloom_get_platform() -> f64 { 4.0 }
1849
+
1850
+ /// Preferred OS language packed as `c0*256+c1` (ISO-639 primary subtag), from $LANG/$LC_*.
1851
+ #[no_mangle]
1852
+ pub extern "C" fn bloom_get_language() -> f64 {
1853
+ fn pack(code: &str) -> f64 { let l = code.to_ascii_lowercase(); let b = l.as_bytes(); if b.len() >= 2 { (b[0] as f64) * 256.0 + (b[1] as f64) } else { 25966.0 } }
1854
+ let v = std::env::var("LANG").or_else(|_| std::env::var("LC_ALL")).or_else(|_| std::env::var("LC_MESSAGES")).unwrap_or_default();
1855
+ if v.len() >= 2 && !v.starts_with('C') && !v.starts_with("POSIX") { pack(&v) } else { 25966.0 }
1856
+ }
1849
1857
  #[no_mangle]
1850
1858
  pub extern "C" fn bloom_is_any_input_pressed() -> f64 {
1851
1859
  if engine().input.is_any_input_pressed() { 1.0 } else { 0.0 }
@@ -27,7 +27,7 @@ panic = "abort"
27
27
  bloom-shared = { path = "../shared", default-features = false, features = ["mp3"] }
28
28
  image = { version = "0.25", default-features = false, features = ["hdr"] }
29
29
  objc2 = "0.6"
30
- objc2-foundation = { version = "0.3", features = ["NSDate", "NSRunLoop", "NSString", "NSThread"] }
30
+ objc2-foundation = { version = "0.3", features = ["NSDate", "NSRunLoop", "NSString", "NSThread", "NSLocale", "NSArray"] }
31
31
  objc2-app-kit = { version = "0.3", features = ["NSWindow", "NSView", "NSEvent", "NSApplication", "NSScreen", "NSGraphicsContext", "NSRunningApplication", "NSResponder"] }
32
32
  objc2-quartz-core = { version = "0.3", features = ["CAMetalLayer"] }
33
33
  raw-window-handle = "0.6"
@@ -2415,6 +2415,25 @@ pub extern "C" fn bloom_inject_gamepad_button_up(button: f64) {
2415
2415
  }
2416
2416
  #[no_mangle]
2417
2417
  pub extern "C" fn bloom_get_platform() -> f64 { 1.0 }
2418
+
2419
+ /// Return the user's preferred OS language as a packed 2-letter code:
2420
+ /// `c0 * 256 + c1`, where c0/c1 are the ASCII bytes of the lowercased
2421
+ /// ISO-639 primary subtag (e.g. "en-US" -> "en" -> 101*256+110). The script
2422
+ /// subtag is dropped (zh-Hans/zh-Hant both pack as "zh"); callers map that to
2423
+ /// their supported variant. Falls back to "en" when no preference is set.
2424
+ #[no_mangle]
2425
+ pub extern "C" fn bloom_get_language() -> f64 {
2426
+ fn pack(code: &str) -> f64 {
2427
+ let lower = code.to_ascii_lowercase();
2428
+ let b = lower.as_bytes();
2429
+ if b.len() >= 2 { (b[0] as f64) * 256.0 + (b[1] as f64) } else { 101.0 * 256.0 + 110.0 }
2430
+ }
2431
+ let langs = objc2_foundation::NSLocale::preferredLanguages();
2432
+ match langs.firstObject() {
2433
+ Some(s) => pack(&s.to_string()),
2434
+ None => pack("en"),
2435
+ }
2436
+ }
2418
2437
  #[no_mangle]
2419
2438
  pub extern "C" fn bloom_is_any_input_pressed() -> f64 {
2420
2439
  if engine().input.is_any_input_pressed() { 1.0 } else { 0.0 }
@@ -22,7 +22,7 @@ panic = "abort"
22
22
  [dependencies]
23
23
  bloom-shared = { path = "../shared", default-features = false, features = ["mp3"] }
24
24
  objc2 = "0.6"
25
- objc2-foundation = { version = "0.3", features = ["NSDate", "NSRunLoop", "NSString", "NSThread", "NSObject"] }
25
+ objc2-foundation = { version = "0.3", features = ["NSDate", "NSRunLoop", "NSString", "NSThread", "NSObject", "NSLocale", "NSArray"] }
26
26
  raw-window-handle = "0.6"
27
27
  wgpu = "29"
28
28
  # Needed by bloom_set_env_clear_from_hdr (HDR env-map decode); mirrors macOS.
@@ -1,6 +1,6 @@
1
1
  use bloom_shared::engine::EngineState;
2
2
  use bloom_shared::renderer::Renderer;
3
- use bloom_shared::string_header::str_from_header;
3
+ use bloom_shared::string_header::{str_from_header, alloc_perry_string};
4
4
  use bloom_shared::audio::{parse_wav, parse_ogg, parse_mp3, SoundData};
5
5
 
6
6
  use objc2::encode::{Encode, Encoding, RefEncode};
@@ -3046,6 +3046,14 @@ pub extern "C" fn bloom_inject_gamepad_button_up(button: f64) {
3046
3046
  }
3047
3047
  #[no_mangle]
3048
3048
  pub extern "C" fn bloom_get_platform() -> f64 { 6.0 }
3049
+
3050
+ /// Preferred OS language packed as `c0*256+c1` (ISO-639 primary subtag). See macos lib for format.
3051
+ #[no_mangle]
3052
+ pub extern "C" fn bloom_get_language() -> f64 {
3053
+ fn pack(code: &str) -> f64 { let l = code.to_ascii_lowercase(); let b = l.as_bytes(); if b.len() >= 2 { (b[0] as f64) * 256.0 + (b[1] as f64) } else { 25966.0 } }
3054
+ let langs = objc2_foundation::NSLocale::preferredLanguages();
3055
+ match langs.firstObject() { Some(s) => pack(&s.to_string()), None => 25966.0 }
3056
+ }
3049
3057
  #[no_mangle]
3050
3058
  pub extern "C" fn bloom_get_crown_rotation() -> f64 {
3051
3059
  engine().input.consume_crown_rotation()
@@ -3328,3 +3336,137 @@ pub extern "C" fn bloom_set_ssgi_enabled(enabled: f64) {
3328
3336
  pub extern "C" fn bloom_set_ssgi_intensity(intensity: f64) {
3329
3337
  engine().renderer.set_ssgi_intensity(intensity as f32);
3330
3338
  }
3339
+
3340
+ // ── Ported from native/macos for tvOS FFI parity (render textures, profiler, DOF/SSGI, splat) ──
3341
+ #[no_mangle]
3342
+ pub extern "C" fn bloom_begin_texture_mode(handle: f64) {
3343
+ let eng = engine();
3344
+ let (w, h, bg_idx) = match eng.textures.render_textures.get(handle) {
3345
+ Some(rt) => {
3346
+ let tex_handle = rt.texture_handle;
3347
+ match eng.textures.textures.get(tex_handle) {
3348
+ Some(td) => (rt.width, rt.height, td.bind_group_idx as usize),
3349
+ None => return,
3350
+ }
3351
+ }
3352
+ None => return,
3353
+ };
3354
+ if let Some(texture) = eng.renderer.get_texture_ref(bg_idx) {
3355
+ // We need to call begin_texture_mode with a reference to the texture,
3356
+ // but get_texture_ref borrows renderer immutably. Clone the texture view
3357
+ // data we need first, then call the mutable method.
3358
+ let color_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
3359
+ // Create depth texture for this RT.
3360
+ let depth_tex = eng.renderer.device.create_texture(&wgpu::TextureDescriptor {
3361
+ label: Some("rt_depth"), size: wgpu::Extent3d { width: w, height: h, depth_or_array_layers: 1 },
3362
+ mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2,
3363
+ format: wgpu::TextureFormat::Depth32Float, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
3364
+ view_formats: &[],
3365
+ });
3366
+ let depth_view = depth_tex.create_view(&wgpu::TextureViewDescriptor::default());
3367
+ eng.renderer.rt_color_view = Some(color_view);
3368
+ eng.renderer.rt_depth_view = Some(depth_view);
3369
+ eng.renderer.rt_depth_texture = Some(depth_tex);
3370
+ eng.renderer.rt_width = w;
3371
+ eng.renderer.rt_height = h;
3372
+ }
3373
+ }
3374
+
3375
+ #[no_mangle]
3376
+ pub extern "C" fn bloom_end_texture_mode() {
3377
+ engine().renderer.end_texture_mode();
3378
+ }
3379
+
3380
+ #[no_mangle]
3381
+ pub extern "C" fn bloom_get_render_texture_texture(handle: f64) -> f64 {
3382
+ engine().textures.get_render_texture_texture(handle)
3383
+ }
3384
+
3385
+ // Q1: Render texture FFI — create GPU textures for render-to-texture.
3386
+ #[no_mangle]
3387
+ pub extern "C" fn bloom_load_render_texture(width: f64, height: f64) -> f64 {
3388
+ let w = width as u32;
3389
+ let h = height as u32;
3390
+ let eng = engine();
3391
+ let rt_handle = eng.textures.load_render_texture(w, h);
3392
+
3393
+ // Create the GPU texture via the renderer's public method.
3394
+ let (bind_group_idx, _tex_vec_idx) = eng.renderer.create_render_texture(w, h);
3395
+
3396
+ // Register as a texture handle so drawTexture can sample it.
3397
+ let tex_handle = eng.textures.textures.alloc(bloom_shared::textures::TextureData {
3398
+ bind_group_idx, width: w, height: h,
3399
+ });
3400
+ eng.textures.set_render_texture_handle(rt_handle, tex_handle);
3401
+
3402
+ rt_handle
3403
+ }
3404
+
3405
+ #[no_mangle]
3406
+ pub extern "C" fn bloom_unload_render_texture(handle: f64) {
3407
+ engine().textures.unload_render_texture(handle);
3408
+ }
3409
+
3410
+ /// Phase 8 — frame history for the histogram. Header-prefixed string,
3411
+ /// one line per frame "<cpu_us>|<gpu_us>", oldest first, up to 120
3412
+ /// lines.
3413
+ #[no_mangle]
3414
+ pub extern "C" fn bloom_profiler_frame_history() -> *const u8 {
3415
+ let hist = engine().profiler.frame_history();
3416
+ let mut s = String::with_capacity(hist.len() * 24);
3417
+ for (cpu, gpu) in &hist {
3418
+ s.push_str(&format!("{:.2}|{:.2}\n", cpu, gpu));
3419
+ }
3420
+ alloc_perry_string(&s)
3421
+ }
3422
+
3423
+ #[no_mangle]
3424
+ pub extern "C" fn bloom_profiler_overlay_text() -> *const u8 {
3425
+ let snap = engine().profiler.snapshot();
3426
+ let mut s = String::with_capacity(snap.len() * 48);
3427
+ for (label, cpu, gpu) in &snap {
3428
+ s.push_str(label);
3429
+ s.push('|');
3430
+ s.push_str(&format!("{:.2}", cpu));
3431
+ s.push('|');
3432
+ match gpu {
3433
+ Some(g) => s.push_str(&format!("{:.2}", g)),
3434
+ None => s.push_str("-1"),
3435
+ }
3436
+ s.push('\n');
3437
+ }
3438
+ alloc_perry_string(&s)
3439
+ }
3440
+
3441
+ #[no_mangle]
3442
+ pub extern "C" fn bloom_set_dof(enabled: f64, focus_distance: f64, aperture: f64) {
3443
+ let r = &mut engine().renderer;
3444
+ r.set_dof_enabled(enabled != 0.0);
3445
+ r.set_dof_focus_distance(focus_distance as f32);
3446
+ r.set_dof_aperture(aperture as f32);
3447
+ }
3448
+
3449
+ #[no_mangle]
3450
+ pub extern "C" fn bloom_set_ssgi_radius(radius: f64) {
3451
+ engine().renderer.set_ssgi_radius(radius as f32);
3452
+ }
3453
+
3454
+ /// Phase 8 — formatted per-pass overlay text. Returns a Perry-style
3455
+ /// header-prefixed string (12-byte header: len, cap, flags, then
3456
+ /// UTF-8 bytes) with one line per pass sorted by CPU time descending:
3457
+ ///
3458
+ /// "<label>|<cpu_us>|<gpu_us_or_-1>\n..."
3459
+ ///
3460
+ /// The TS overlay splits on \n then | per line. Games call this once
3461
+ /// per overlay-draw frame.
3462
+ /// Phase 7 — submit a world-space impulse. The renderer's per-frame
3463
+ /// compute pass decays + accumulates submissions into a 256×256
3464
+ /// R32Float texture covering a 128 m centred square; materials that
3465
+ /// sample `impulse_tex` (group 4 binding 4) read the result. Up to 16
3466
+ /// splats per frame are accepted — overflow is dropped silently.
3467
+ #[no_mangle]
3468
+ pub extern "C" fn bloom_splat_impulse(x: f64, z: f64, radius: f64, strength: f64) {
3469
+ engine().renderer.impulse_field.submit_splat(
3470
+ x as f32, z as f32, radius as f32, strength as f32,
3471
+ );
3472
+ }
@@ -206,6 +206,10 @@ pub extern "C" fn perry_scene_will_connect(_scene: *const c_void) {}
206
206
  #[no_mangle]
207
207
  pub extern "C" fn bloom_get_platform() -> f64 { 8.0 }
208
208
 
209
+ /// Preferred OS language packed as `c0*256+c1`. TODO: real per-OS detection; returns "en" for now.
210
+ #[no_mangle]
211
+ pub extern "C" fn bloom_get_language() -> f64 { 25966.0 }
212
+
209
213
  #[no_mangle]
210
214
  pub extern "C" fn bloom_get_crown_rotation() -> f64 { consume_crown() }
211
215
 
@@ -1937,6 +1937,9 @@ pub fn bloom_get_platform() -> f64 {
1937
1937
  7.0 // Web platform ID
1938
1938
  }
1939
1939
 
1940
+ #[wasm_bindgen]
1941
+ pub fn bloom_get_language() -> f64 { 25966.0 } // TODO: navigator.language via JS host glue
1942
+
1940
1943
  #[wasm_bindgen]
1941
1944
  pub fn bloom_is_any_input_pressed() -> f64 {
1942
1945
  if engine().input.is_any_input_pressed() { 1.0 } else { 0.0 }
@@ -1635,6 +1635,10 @@ pub extern "C" fn bloom_inject_gamepad_button_up(button: f64) {
1635
1635
  }
1636
1636
  #[no_mangle]
1637
1637
  pub extern "C" fn bloom_get_platform() -> f64 { 3.0 }
1638
+
1639
+ /// Preferred OS language packed as `c0*256+c1`. TODO: real per-OS detection; returns "en" for now.
1640
+ #[no_mangle]
1641
+ pub extern "C" fn bloom_get_language() -> f64 { 25966.0 }
1638
1642
  #[no_mangle]
1639
1643
  pub extern "C" fn bloom_is_any_input_pressed() -> f64 {
1640
1644
  if engine().input.is_any_input_pressed() { 1.0 } else { 0.0 }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bloomengine/engine",
3
- "version": "0.4.9",
3
+ "version": "0.4.11",
4
4
  "description": "Bloom Engine: native TypeScript game engine compiled by Perry",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -251,6 +251,7 @@
251
251
  { "name": "bloom_inject_gamepad_button_down","params": ["f64"], "returns": "void" },
252
252
  { "name": "bloom_inject_gamepad_button_up","params": ["f64"], "returns": "void" },
253
253
  { "name": "bloom_get_platform", "params": [], "returns": "f64" },
254
+ { "name": "bloom_get_language", "params": [], "returns": "f64" },
254
255
  { "name": "bloom_is_any_input_pressed", "params": [], "returns": "f64" },
255
256
  { "name": "bloom_get_crown_rotation", "params": [], "returns": "f64" },
256
257
 
package/src/core/index.ts CHANGED
@@ -97,6 +97,7 @@ declare function bloom_inject_gamepad_axis(axis: number, value: number): void;
97
97
  declare function bloom_inject_gamepad_button_down(button: number): void;
98
98
  declare function bloom_inject_gamepad_button_up(button: number): void;
99
99
  declare function bloom_get_platform(): number;
100
+ declare function bloom_get_language(): number;
100
101
  declare function bloom_is_any_input_pressed(): number;
101
102
  declare function bloom_get_crown_rotation(): number;
102
103
 
@@ -822,6 +823,11 @@ export const Platform = { UNKNOWN: 0, MACOS: 1, IOS: 2, WINDOWS: 3, LINUX: 4, AN
822
823
 
823
824
  export function getPlatform(): number { return bloom_get_platform(); }
824
825
 
826
+ /// User's preferred OS language as a packed 2-letter code (`c0 * 256 + c1`,
827
+ /// ASCII of the lowercased ISO-639 primary subtag, e.g. "en" = 101*256+110).
828
+ /// Script subtags are dropped (zh-Hans -> "zh"); callers map to their variant.
829
+ export function getLanguage(): number { return bloom_get_language(); }
830
+
825
831
  export function isMobile(): boolean {
826
832
  const p = bloom_get_platform();
827
833
  return p === 2 || p === 5;