diff --git a/Cargo.lock b/Cargo.lock index 97129d8..4182473 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,6 +255,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" +[[package]] +name = "build-target" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78e2ceaf91e22593e194211930aea78a41af58e49e872474ebf4335bf649aad1" + [[package]] name = "bumpalo" version = "3.20.2" @@ -325,6 +331,12 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "cargo-emit" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1582e1c9e755dd6ad6b224dcffb135d199399a4568d454bd89fe515ca8425695" + [[package]] name = "cc" version = "1.2.62" @@ -343,6 +355,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg-tt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae391eeac9af9386516ee053e108880e836eead67b41af8fb5430fc8e7968be9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cfg_aliases" version = "0.2.1" @@ -437,6 +460,15 @@ dependencies = [ "libc", ] +[[package]] +name = "coreclr-hosting-shared" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee716bcab7e6bf9589fcf9373c483b24fdf87daa4694996bf5f099552786b847" +dependencies = [ + "cty", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -496,6 +528,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "cstr" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68523903c8ae5aacfa32a0d9ae60cadeb764e1da14ee0d26b1f3089f13a54636" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "ctrlc" version = "3.5.2" @@ -507,6 +549,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + [[package]] name = "cursor-icon" version = "1.2.0" @@ -545,6 +593,26 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "destruct-drop" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eef803a96c15c37e6c7dba7636950982e024fbbe00d1b07cfe60e01ab01e0e0" +dependencies = [ + "destruct-drop-derive", +] + +[[package]] +name = "destruct-drop-derive" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133a7fa5cffeec6867fb2847335ec2d688f5bbee6318889d2a137ce1d226b180" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -593,6 +661,29 @@ dependencies = [ "libloading", ] +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "document-features" version = "0.2.12" @@ -681,6 +772,26 @@ dependencies = [ "syn", ] +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -717,6 +828,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ffi-opaque" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec54ac60a7f2ee9a97cad9946f9bf629a3bc6a7ae59e68983dc9318f5a54b81a" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -750,6 +867,20 @@ dependencies = [ "serde", ] +[[package]] +name = "fn-ptr" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9058453bc5be9955199924aa93386a94d9571b4f02e679fbe76c4b7a2474995e" +dependencies = [ + "build-target", + "cargo-emit", + "cfg-tt", + "ffi-opaque", + "konst", + "rustc_version", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1190,6 +1321,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hostfxr-sys" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a7d0663771c2a4fc70589e6cf1f63a2fd73fc28dc5d62a77f74ab10da16cf47" +dependencies = [ + "coreclr-hosting-shared", + "dlopen2", + "enum-map", +] + [[package]] name = "http" version = "1.4.1" @@ -1565,6 +1707,16 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" +[[package]] +name = "konst" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f660d5f887e3562f9ab6f4a14988795b694099d66b4f5dedc02d197ba9becb1d" +dependencies = [ + "const_panic", + "typewit", +] + [[package]] name = "lalrpop" version = "0.22.2" @@ -1945,6 +2097,25 @@ dependencies = [ "jni-sys 0.3.1", ] +[[package]] +name = "netcorehost" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce5f27bff37e89e0b6f63b86af117861d1f049c8896a16f661f587bfd139670" +dependencies = [ + "coreclr-hosting-shared", + "cstr", + "derive_more", + "destruct-drop", + "enum-map", + "fn-ptr", + "hostfxr-sys", + "num_enum", + "once_cell", + "thiserror 2.0.18", + "widestring", +] + [[package]] name = "new_debug_unreachable" version = "1.0.6" @@ -4179,6 +4350,28 @@ dependencies = [ "safe_arch", ] +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" @@ -4188,6 +4381,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows" version = "0.62.2" @@ -4560,6 +4759,7 @@ dependencies = [ "ze_log", "ze_physics", "ze_renderer", + "ze_scripting_cs", ] [[package]] @@ -4609,6 +4809,7 @@ dependencies = [ "rapier2d", "ze_core", "ze_ecs", + "ze_scripting_cs", ] [[package]] @@ -4626,6 +4827,21 @@ dependencies = [ "ze_log", ] +[[package]] +name = "ze_scripting_cs" +version = "0.1.0" +dependencies = [ + "fn-ptr", + "netcorehost", + "schemars", + "serde", + "shipyard", + "ze_core", + "ze_ecs", + "ze_input", + "ze_renderer", +] + [[package]] name = "zerocopy" version = "0.8.48" diff --git a/Cargo.toml b/Cargo.toml index 378c1a3..478af4c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "ZeroEngine/crates/ze_log", "ZeroEngine/crates/ze_physics", "ZeroEngine/crates/ze_renderer", + "ZeroEngine/crates/ze_scripting_cs", "ZeroEngine/crates/ze_ecs", "ZeroEngine/crates/zeroengine", ] @@ -42,3 +43,28 @@ codegen-units = 1 strip = "symbols" [profile.dist.package."*"] opt-level = "z" # 3, "s", "z" + +[workspace.lints.rust] +unsafe_code = "allow" + +[workspace.lints.clippy] +suspicious = { level = "warn", priority = -1 } +style = { level = "warn", priority = -1 } +perf = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } +nursery = { level = "warn", priority = -1 } + +unwrap_used = "warn" +missing_assert_message = "warn" +float_cmp_const = "warn" +fn_to_numeric_cast_any = "warn" +todo = "warn" + +cast_possible_truncation = "allow" +cast_precision_loss = "allow" +cast_sign_loss = "allow" +module_name_repetitions = "allow" +missing_errors_doc = "allow" +missing_panics_doc = "allow" +similar_names = "allow" +must_use_candidate = "allow" diff --git a/ZeroEngine/api/cs/Components/Camera.cs b/ZeroEngine/api/cs/Components/Camera.cs new file mode 100644 index 0000000..990ed92 --- /dev/null +++ b/ZeroEngine/api/cs/Components/Camera.cs @@ -0,0 +1,6 @@ +namespace ZeroEngine; + +public sealed class Camera : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.Camera; +} diff --git a/ZeroEngine/api/cs/Components/Children.cs b/ZeroEngine/api/cs/Components/Children.cs new file mode 100644 index 0000000..27d5b79 --- /dev/null +++ b/ZeroEngine/api/cs/Components/Children.cs @@ -0,0 +1,6 @@ +namespace ZeroEngine; + +public sealed class Children : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.Children; +} diff --git a/ZeroEngine/api/cs/Components/Collider.cs b/ZeroEngine/api/cs/Components/Collider.cs new file mode 100644 index 0000000..f481cb9 --- /dev/null +++ b/ZeroEngine/api/cs/Components/Collider.cs @@ -0,0 +1,6 @@ +namespace ZeroEngine; + +public sealed class Collider : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.Collider; +} diff --git a/ZeroEngine/api/cs/Components/ComponentType.cs b/ZeroEngine/api/cs/Components/ComponentType.cs new file mode 100644 index 0000000..8a6808d --- /dev/null +++ b/ZeroEngine/api/cs/Components/ComponentType.cs @@ -0,0 +1,17 @@ +namespace ZeroEngine; + +internal enum ComponentType : uint +{ + Name = 1, + Tag = 2, + Transform = 3, + Parent = 4, + Children = 5, + Inactive = 6, + Rigidbody = 7, + PhysicsSettings = 8, + Collider = 9, + Sprite = 10, + Camera = 11, + Script = 12, +} diff --git a/ZeroEngine/api/cs/Components/Inactive.cs b/ZeroEngine/api/cs/Components/Inactive.cs new file mode 100644 index 0000000..540108a --- /dev/null +++ b/ZeroEngine/api/cs/Components/Inactive.cs @@ -0,0 +1,6 @@ +namespace ZeroEngine; + +public sealed class Inactive : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.Inactive; +} diff --git a/ZeroEngine/api/cs/Components/Name.cs b/ZeroEngine/api/cs/Components/Name.cs new file mode 100644 index 0000000..53550c8 --- /dev/null +++ b/ZeroEngine/api/cs/Components/Name.cs @@ -0,0 +1,6 @@ +namespace ZeroEngine; + +public sealed class Name : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.Name; +} diff --git a/ZeroEngine/api/cs/Components/Parent.cs b/ZeroEngine/api/cs/Components/Parent.cs new file mode 100644 index 0000000..6388a51 --- /dev/null +++ b/ZeroEngine/api/cs/Components/Parent.cs @@ -0,0 +1,6 @@ +namespace ZeroEngine; + +public sealed class Parent : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.Parent; +} diff --git a/ZeroEngine/api/cs/Components/PhysicsSettings.cs b/ZeroEngine/api/cs/Components/PhysicsSettings.cs new file mode 100644 index 0000000..876ee3c --- /dev/null +++ b/ZeroEngine/api/cs/Components/PhysicsSettings.cs @@ -0,0 +1,6 @@ +namespace ZeroEngine; + +public sealed class PhysicsSettings : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.PhysicsSettings; +} diff --git a/ZeroEngine/api/cs/Components/Rigidbody.cs b/ZeroEngine/api/cs/Components/Rigidbody.cs new file mode 100644 index 0000000..4f90bcf --- /dev/null +++ b/ZeroEngine/api/cs/Components/Rigidbody.cs @@ -0,0 +1,52 @@ +namespace ZeroEngine; + +public sealed unsafe class Rigidbody : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.Rigidbody; + + public Vector2 Velocity + { + get + { + float x = 0.0f; + float y = 0.0f; + EngineAPI.Current->get_velocity(EntityId, &x, &y); + return new Vector2(x, y); + } + } + + public void Add2DForce(float x, float y) + { + EngineAPI.Current->add_2d_impulse(EntityId, x, y); + } + + public void Add2DForce(Vector2 force) + { + Add2DForce(force.X, force.Y); + } + + public void Add2DForceWithMax(Vector2 force, Vector2 maxVelocity) + { + Add2DForceWithMax(force.X, force.Y, maxVelocity.X, maxVelocity.Y); + } + + public void Add2DForceWithMax(float x, float y, float maxX, float maxY) + { + var currentVel = Velocity; + float requiredX = x; + + if (x > 0.0f && currentVel.X >= maxX) requiredX = 0.0f; + if (x < 0.0f && currentVel.X <= -maxX) requiredX = 0.0f; + + float requiredY = y; + if (y > 0.0f && currentVel.Y >= maxY) requiredY = 0.0f; + if (y < 0.0f && currentVel.Y <= -maxY) requiredY = 0.0f; + + if (requiredX == 0.0f && requiredY == 0.0f) + { + return; + } + + Add2DForce(requiredX, requiredY); + } +} \ No newline at end of file diff --git a/ZeroEngine/api/cs/Components/ScriptComponent.cs b/ZeroEngine/api/cs/Components/ScriptComponent.cs new file mode 100644 index 0000000..8e02976 --- /dev/null +++ b/ZeroEngine/api/cs/Components/ScriptComponent.cs @@ -0,0 +1,6 @@ +namespace ZeroEngine; + +public sealed class ScriptComponent : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.Script; +} diff --git a/ZeroEngine/api/cs/Components/Sprite.cs b/ZeroEngine/api/cs/Components/Sprite.cs new file mode 100644 index 0000000..ba7c9a2 --- /dev/null +++ b/ZeroEngine/api/cs/Components/Sprite.cs @@ -0,0 +1,6 @@ +namespace ZeroEngine; + +public sealed class Sprite : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.Sprite; +} diff --git a/ZeroEngine/api/cs/Components/Tag.cs b/ZeroEngine/api/cs/Components/Tag.cs new file mode 100644 index 0000000..7b38e4b --- /dev/null +++ b/ZeroEngine/api/cs/Components/Tag.cs @@ -0,0 +1,6 @@ +namespace ZeroEngine; + +public sealed class Tag : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.Tag; +} diff --git a/ZeroEngine/api/cs/Components/Transform.cs b/ZeroEngine/api/cs/Components/Transform.cs new file mode 100644 index 0000000..1f7914a --- /dev/null +++ b/ZeroEngine/api/cs/Components/Transform.cs @@ -0,0 +1,6 @@ +namespace ZeroEngine; + +public sealed class Transform : ZEComponent +{ + internal override ComponentType ComponentType => ComponentType.Transform; +} diff --git a/ZeroEngine/api/cs/Components/ZEComponent.cs b/ZeroEngine/api/cs/Components/ZEComponent.cs new file mode 100644 index 0000000..7dbd4ff --- /dev/null +++ b/ZeroEngine/api/cs/Components/ZEComponent.cs @@ -0,0 +1,17 @@ +namespace ZeroEngine; + +public abstract class ZEComponent +{ + public ulong EntityId { get; private set; } + + public uint EntityIndex => (uint)(EntityId & 0xFFFFFFFF); + + public uint EntityGeneration => (uint)(EntityId >> 32); + + internal abstract ComponentType ComponentType { get; } + + internal void Bind(ulong entityId) + { + EntityId = entityId; + } +} diff --git a/ZeroEngine/api/cs/EngineAPI.cs b/ZeroEngine/api/cs/EngineAPI.cs new file mode 100644 index 0000000..5a021d8 --- /dev/null +++ b/ZeroEngine/api/cs/EngineAPI.cs @@ -0,0 +1,44 @@ +namespace ZeroEngine; + +public unsafe struct EngineAPI +{ + public delegate* unmanaged is_key_pressed; + public delegate* unmanaged is_key_just_pressed; + public delegate* unmanaged is_key_released; + public delegate* unmanaged is_key_just_released; + public delegate* unmanaged is_mouse_button_pressed; + public delegate* unmanaged is_mouse_button_just_pressed; + public delegate* unmanaged get_mouse_position; + public delegate* unmanaged get_mouse_delta; + public delegate* unmanaged get_time_state_ptr; + public delegate* unmanaged has_component; + public delegate* unmanaged get_velocity; + public delegate* unmanaged add_2d_force; + public delegate* unmanaged add_2d_impulse; + + private static EngineAPI* current; + + internal static void Initialize(EngineAPI* api) + { + current = api; + Time.Initialize(api->get_time_state_ptr()); + } + + internal static bool HasComponent(ulong entity, ComponentType componentType) + { + return Current->has_component(entity, (uint)componentType); + } + + internal static EngineAPI* Current + { + get + { + if (current == null) + { + throw new InvalidOperationException("ZeroEngine API was used before OnEngineInit."); + } + + return current; + } + } +} diff --git a/ZeroEngine/api/cs/GlobalUsings.cs b/ZeroEngine/api/cs/GlobalUsings.cs new file mode 100644 index 0000000..6ab8c31 --- /dev/null +++ b/ZeroEngine/api/cs/GlobalUsings.cs @@ -0,0 +1 @@ +global using System.Numerics; diff --git a/ZeroEngine/api/cs/Input.cs b/ZeroEngine/api/cs/Input.cs new file mode 100644 index 0000000..352ca55 --- /dev/null +++ b/ZeroEngine/api/cs/Input.cs @@ -0,0 +1,32 @@ +namespace ZeroEngine; + +public static unsafe class Input +{ + public static bool IsKeyPressed(KeyCode key) => EngineAPI.Current->is_key_pressed((int)key); + + public static bool IsKeyJustPressed(KeyCode key) => EngineAPI.Current->is_key_just_pressed((int)key); + + public static bool IsKeyReleased(KeyCode key) => EngineAPI.Current->is_key_released((int)key); + + public static bool IsKeyJustReleased(KeyCode key) => EngineAPI.Current->is_key_just_released((int)key); + + public static bool IsMouseButtonPressed(int button) => EngineAPI.Current->is_mouse_button_pressed(button); + + public static bool IsMouseButtonJustPressed(int button) => EngineAPI.Current->is_mouse_button_just_pressed(button); + + public static Vector2 GetMousePosition() + { + float x = 0.0f; + float y = 0.0f; + EngineAPI.Current->get_mouse_position(&x, &y); + return new Vector2(x, y); + } + + public static Vector2 GetMouseDelta() + { + float x = 0.0f; + float y = 0.0f; + EngineAPI.Current->get_mouse_delta(&x, &y); + return new Vector2(x, y); + } +} diff --git a/ZeroEngine/api/cs/KeyCode.cs b/ZeroEngine/api/cs/KeyCode.cs new file mode 100644 index 0000000..3112c4b --- /dev/null +++ b/ZeroEngine/api/cs/KeyCode.cs @@ -0,0 +1,59 @@ +namespace ZeroEngine; + +public enum KeyCode +{ + Escape = 0, + Space = 1, + Q = 2, + W = 3, + E = 4, + R = 5, + T = 6, + Y = 7, + U = 8, + I = 9, + O = 10, + P = 11, + A = 12, + S = 13, + D = 14, + F = 15, + G = 16, + H = 17, + J = 18, + K = 19, + L = 20, + Z = 21, + X = 22, + C = 23, + V = 24, + B = 25, + N = 26, + M = 27, + Enter = 28, + LCtrl = 29, + LShift = 30, + K1 = 31, + K2 = 32, + K3 = 33, + K4 = 34, + K5 = 35, + K6 = 36, + K7 = 37, + K8 = 38, + K9 = 39, + K0 = 40, + KF1 = 41, + KF2 = 42, + KF3 = 43, + KF4 = 44, + KF5 = 45, + KF6 = 46, + KF7 = 47, + KF8 = 48, + KF9 = 49, + KF10 = 50, + KF11 = 51, + KF12 = 52, + Unknown = 511, +} diff --git a/ZeroEngine/api/cs/Physics.cs b/ZeroEngine/api/cs/Physics.cs new file mode 100644 index 0000000..1cfefb2 --- /dev/null +++ b/ZeroEngine/api/cs/Physics.cs @@ -0,0 +1,16 @@ +namespace ZeroEngine; + +public static unsafe class Physics +{ + public static Vector2 GetVelocity(ulong entity) + { + float x = 0.0f; + float y = 0.0f; + EngineAPI.Current->get_velocity(entity, &x, &y); + return new Vector2(x, y); + } + + public static void Add2DForce(ulong entity, float x, float y) => EngineAPI.Current->add_2d_force(entity, x, y); + + public static void Add2DImpulse(ulong entity, float x, float y) => EngineAPI.Current->add_2d_impulse(entity, x, y); +} diff --git a/ZeroEngine/api/cs/Time.cs b/ZeroEngine/api/cs/Time.cs new file mode 100644 index 0000000..53e50d6 --- /dev/null +++ b/ZeroEngine/api/cs/Time.cs @@ -0,0 +1,60 @@ +using System.Runtime.InteropServices; + +namespace ZeroEngine; + +[StructLayout(LayoutKind.Sequential)] +public struct TimeState +{ + public float delta_time; + public float fixed_delta_time; + public float unscaled_delta_time; + public float time_scale; + public double time_since_startup; + public double unscaled_time_since_startup; + public double fixed_time; + public ulong frame_count; + public ulong fixed_frame_count; + public byte is_fixed_update; +} + +public static unsafe class Time +{ + private static TimeState* _state; + + public static float DeltaTime => State->delta_time; + + public static float FixedDeltaTime => State->fixed_delta_time; + + public static float UnscaledDeltaTime => State->unscaled_delta_time; + + public static float TimeScale => State->time_scale; + + public static double TimeSinceStartup => State->time_since_startup; + + public static double UnscaledTimeSinceStartup => State->unscaled_time_since_startup; + + public static double RealtimeSinceStartup => State->unscaled_time_since_startup; + + public static double FixedTime => State->fixed_time; + + public static ulong FrameCount => State->frame_count; + + public static ulong FixedFrameCount => State->fixed_frame_count; + + public static bool IsFixedUpdate => State->is_fixed_update != 0; + + internal static void Initialize(TimeState* statePtr) => _state = statePtr; + + private static TimeState* State + { + get + { + if (_state == null) + { + throw new InvalidOperationException("ZeroEngine Time was used before engine initialization."); + } + + return _state; + } + } +} diff --git a/ZeroEngine/api/cs/ZEScript.cs b/ZeroEngine/api/cs/ZEScript.cs new file mode 100644 index 0000000..33571b3 --- /dev/null +++ b/ZeroEngine/api/cs/ZEScript.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; + +namespace ZeroEngine; + +public abstract class ZEScript +{ + private static readonly Dictionary _instances = new(); + + public ulong EntityId { get; protected set; } + + public uint EntityIndex => (uint)(EntityId & 0xFFFFFFFF); + + public uint EntityGeneration => (uint)(EntityId >> 32); + + public T GetComponent() + where T : ZEComponent, new() + { + var component = new T(); + component.Bind(EntityId); + + if (!EngineAPI.HasComponent(EntityId, component.ComponentType)) + { + throw new InvalidOperationException( + $"Entity {EntityIndex}.{EntityGeneration} does not have component {typeof(T).Name}."); + } + + return component; + } + + public virtual void OnCreate() { } + + public virtual void OnStart() { } + + public virtual void OnDestroy() { } + + public virtual void OnUpdate() { } + + public virtual void OnFixedUpdate() { } + + public virtual void OnEnable() { } + + public virtual void OnDisable() { } + + public virtual void OnContactEnter(ulong otherEntity) { } + + public virtual void OnContactStay(ulong otherEntity) { } + + public virtual void OnContactExit(ulong otherEntity) { } + + public virtual void OnSensorEnter(ulong otherEntity) { } + + public virtual void OnSensorStay(ulong otherEntity) { } + + public virtual void OnSensorExit(ulong otherEntity) { } + + [UnmanagedCallersOnly] + public static unsafe void NativeOnEngineInit( + ulong entityId, + EngineAPI* api, + byte* classPathPtr, + int classPathLength) + { + EngineAPI.Initialize(api); + var classPath = Encoding.UTF8.GetString(classPathPtr, classPathLength); + + if (_instances.TryGetValue(entityId, out var existing) && existing.GetType().FullName == classPath) + { + return; + } + + var scriptType = ResolveScriptType(classPath); + var instance = (ZEScript)Activator.CreateInstance(scriptType)!; + instance.EntityId = entityId; + _instances[entityId] = instance; + } + + [UnmanagedCallersOnly] + public static void NativeOnCreate(ulong entityId) => Lookup(entityId).OnCreate(); + + [UnmanagedCallersOnly] + public static void NativeOnStart(ulong entityId) => Lookup(entityId).OnStart(); + + [UnmanagedCallersOnly] + public static void NativeOnDestroy(ulong entityId) + { + if (!_instances.Remove(entityId, out var instance)) + { + return; + } + + instance.OnDestroy(); + } + + [UnmanagedCallersOnly] + public static void NativeOnUpdate(ulong entityId) => Lookup(entityId).OnUpdate(); + + [UnmanagedCallersOnly] + public static void NativeOnFixedUpdate(ulong entityId) => Lookup(entityId).OnFixedUpdate(); + + [UnmanagedCallersOnly] + public static void NativeOnEnable(ulong entityId) => Lookup(entityId).OnEnable(); + + [UnmanagedCallersOnly] + public static void NativeOnDisable(ulong entityId) => Lookup(entityId).OnDisable(); + + [UnmanagedCallersOnly] + public static void NativeOnContactEnter(ulong entityId, ulong otherEntity) => + Lookup(entityId).OnContactEnter(otherEntity); + + [UnmanagedCallersOnly] + public static void NativeOnContactStay(ulong entityId, ulong otherEntity) => + Lookup(entityId).OnContactStay(otherEntity); + + [UnmanagedCallersOnly] + public static void NativeOnContactExit(ulong entityId, ulong otherEntity) => + Lookup(entityId).OnContactExit(otherEntity); + + [UnmanagedCallersOnly] + public static void NativeOnSensorEnter(ulong entityId, ulong otherEntity) => + Lookup(entityId).OnSensorEnter(otherEntity); + + [UnmanagedCallersOnly] + public static void NativeOnSensorStay(ulong entityId, ulong otherEntity) => + Lookup(entityId).OnSensorStay(otherEntity); + + [UnmanagedCallersOnly] + public static void NativeOnSensorExit(ulong entityId, ulong otherEntity) => + Lookup(entityId).OnSensorExit(otherEntity); + + private static ZEScript Lookup(ulong entityId) + { + if (!_instances.TryGetValue(entityId, out var instance)) + { + throw new InvalidOperationException($"No script instance registered for entity id {entityId}."); + } + + return instance; + } + + private static Type ResolveScriptType(string classPath) + { + var scriptType = Type.GetType($"{classPath}, Scripts", throwOnError: false) + ?? Assembly.GetExecutingAssembly().GetType(classPath, throwOnError: false); + + if (scriptType is null) + { + throw new InvalidOperationException($"Could not resolve script type `{classPath}`."); + } + + if (!typeof(ZEScript).IsAssignableFrom(scriptType)) + { + throw new InvalidOperationException($"Script type `{classPath}` must inherit from ZEScript."); + } + + return scriptType; + } +} diff --git a/ZeroEngine/crates/ze_app/Cargo.toml b/ZeroEngine/crates/ze_app/Cargo.toml index f95b732..330f15e 100644 --- a/ZeroEngine/crates/ze_app/Cargo.toml +++ b/ZeroEngine/crates/ze_app/Cargo.toml @@ -7,12 +7,16 @@ authors.workspace = true version.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] ze_core = { path = "../ze_core" } ze_input = { path = "../ze_input" } ze_log = { path = "../ze_log" } ze_ecs = { path = "../ze_ecs" } ze_physics = { path = "../ze_physics" } +ze_scripting_cs = { path = "../ze_scripting_cs" } winit = "0.30.13" ze_renderer = { version = "0.1.0", path = "../ze_renderer" } tokio = { version = "1.52.3", features = ["full"] } diff --git a/ZeroEngine/crates/ze_app/src/lib.rs b/ZeroEngine/crates/ze_app/src/lib.rs index 5050057..638e6cc 100644 --- a/ZeroEngine/crates/ze_app/src/lib.rs +++ b/ZeroEngine/crates/ze_app/src/lib.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, sync::Arc, time::Instant}; use winit::{ application::ApplicationHandler, @@ -9,9 +9,10 @@ use winit::{ }; use ze_core::{ResourceManager, Result, bail}; use ze_ecs::{Scene, System, registry}; -use ze_input::*; +use ze_input::{Input, ZKeyCode, ZMouseCode}; use ze_physics::PhysicsSystem; use ze_renderer::{EditorCameraSystem, RenderSystem, register_renderer_components}; +use ze_scripting_cs::{ScriptingSystem, register_scripting_components}; const DEFAULT_SCENE_NAME: &str = "main"; @@ -29,6 +30,7 @@ pub struct App { minimized: bool, scenes: HashMap, active_scene: String, + last_frame_time: Instant, resources: ResourceManager, } impl Default for App { @@ -65,6 +67,7 @@ impl App { minimized: false, scenes, active_scene, + last_frame_time: Instant::now(), resources, }) } @@ -118,9 +121,10 @@ pub fn load_main_scene(resources: &ResourceManager) -> Result { Scene::register_defaults(&mut registry); register_renderer_components(&mut registry); + register_scripting_components(&mut registry); let mut scene = Scene::from_path_with_registry( - resources + &resources .game_assets_root() .join(format!("scenes/{DEFAULT_SCENE_NAME}.zescene.json")), registry, @@ -131,7 +135,10 @@ pub fn load_main_scene(resources: &ResourceManager) -> Result { ) })?; scene.add_system(EditorCameraSystem::new()); - scene.add_system(PhysicsSystem::new()); + let scripting_system = ScriptingSystem::new()?; + let scripting_runtime = scripting_system.runtime(); + scene.add_system(scripting_system); + scene.add_system(PhysicsSystem::with_scripting(scripting_runtime)); scene.add_system(RenderSystem::new()); Ok(scene) } @@ -176,18 +183,17 @@ impl ApplicationHandler for App { self.window = Some(window); - let renderer = self.renderer.as_mut().unwrap_or_else(|| { - ze_log::error!("Renderer is not initialized!"); - std::process::exit(1); - }); - - renderer.build_ubos_for_objects(2); + self.last_frame_time = Instant::now(); } fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { ze_log::trace!("App update"); event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll); + let now = Instant::now(); + let dt = now.duration_since(self.last_frame_time).as_secs_f32().min(0.05); + self.last_frame_time = now; + if Input::is_key_just_pressed(ZKeyCode::Escape) { ze_log::info!("Exiting..."); event_loop.exit(); // TODO: TEMP @@ -198,12 +204,12 @@ impl ApplicationHandler for App { && !self.occluded && !self.minimized { - if let Err(error) = self.update_active_scene_systems(0.017) { + if let Err(error) = self.update_active_scene_systems(dt) { ze_log::error!("System update failed: {error:?}"); } window.request_redraw(); } - Input::update_globally(|i| i.late_update()); + Input::update_globally(Input::late_update); } fn user_event(&mut self, event_loop: &ActiveEventLoop, event: CustomEvents) { @@ -231,7 +237,7 @@ impl ApplicationHandler for App { self.focused = focused; if !focused { - Input::update_globally(|i| i.reset()); + Input::update_globally(Input::reset); } } WindowEvent::Occluded(occluded) => { @@ -275,18 +281,18 @@ impl ApplicationHandler for App { return; }; - let render_result = scene.with_system_mut::(|render_system, scene| { - render_system.render(scene, renderer, &self.resources) + scene.with_system_mut::(|render_system, scene| { + render_system.render(scene, renderer, &self.resources); }); - let Some(render_result) = render_result else { - ze_log::warn!("No RenderSystem found in scene `{}`", scene.name); - return; - }; + // let Some(render_result) = render_result else { + // ze_log::warn!("No RenderSystem found in scene `{}`", + // scene.name); return; + // }; - if let Err(error) = render_result { - ze_log::error!("Render system error: {error:?}"); - } + // if let Err(error) = render_result { + // ze_log::error!("Render system error: {error:?}"); + // } } } _ => {} diff --git a/ZeroEngine/crates/ze_core/Cargo.toml b/ZeroEngine/crates/ze_core/Cargo.toml index b348b34..3c1da78 100644 --- a/ZeroEngine/crates/ze_core/Cargo.toml +++ b/ZeroEngine/crates/ze_core/Cargo.toml @@ -7,6 +7,9 @@ authors.workspace = true version.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] glam = { version = "0.33.0", features = [ "bytemuck", diff --git a/ZeroEngine/crates/ze_core/build.rs b/ZeroEngine/crates/ze_core/build.rs index 7f85617..e7b3448 100644 --- a/ZeroEngine/crates/ze_core/build.rs +++ b/ZeroEngine/crates/ze_core/build.rs @@ -1,5 +1,9 @@ fn main() { println!("cargo:rerun-if-changed=../../../assets"); - wesl::Wesl::new("../../../assets/shaders/engine") - .build_artifact(&"package::sprite".parse().unwrap(), "engine_sprite"); + wesl::Wesl::new("../../../assets/shaders/engine").build_artifact( + &"package::sprite" + .parse() + .expect("Failed to parse hardcoded engine sprite package name"), + "engine_sprite", + ); } diff --git a/ZeroEngine/crates/ze_core/src/color.rs b/ZeroEngine/crates/ze_core/src/color.rs index aa2c774..55fbd26 100644 --- a/ZeroEngine/crates/ze_core/src/color.rs +++ b/ZeroEngine/crates/ze_core/src/color.rs @@ -46,9 +46,9 @@ impl Color { a: 0.0, }; - pub fn new(r: f32, g: f32, b: f32, a: f32) -> Self { Self { r, g, b, a } } - pub fn rgb(r: f32, g: f32, b: f32) -> Self { Self { r, g, b, a: 1.0 } } - pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self { Self { r, g, b, a } } + pub const fn new(r: f32, g: f32, b: f32, a: f32) -> Self { Self { r, g, b, a } } + pub const fn rgb(r: f32, g: f32, b: f32) -> Self { Self { r, g, b, a: 1.0 } } + pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self { Self { r, g, b, a } } pub fn new_u8(r: u8, g: u8, b: u8, a: u8) -> Self { Self { @@ -111,7 +111,7 @@ impl RgbaInput { } } -fn byte_channel_to_float(channel: u8) -> f32 { channel as f32 / 255.0 } +fn byte_channel_to_float(channel: u8) -> f32 { f32::from(channel) / 255.0 } fn validate_float_rgba(color: [f32; 4]) -> Result<(), &'static str> { if color diff --git a/ZeroEngine/crates/ze_core/src/error.rs b/ZeroEngine/crates/ze_core/src/error.rs index 1c2ee6a..1284836 100644 --- a/ZeroEngine/crates/ze_core/src/error.rs +++ b/ZeroEngine/crates/ze_core/src/error.rs @@ -7,8 +7,8 @@ pub enum ZeroError { impl std::fmt::Display for ZeroError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { - ZeroError::WindowCreationFailed(s) => write!(f, "Window creation failed: {s}"), - ZeroError::Unknown(s) => write!(f, "Unknown error: {s}"), + Self::WindowCreationFailed(s) => write!(f, "Window creation failed: {s}"), + Self::Unknown(s) => write!(f, "Unknown error: {s}"), } } } diff --git a/ZeroEngine/crates/ze_ecs/Cargo.toml b/ZeroEngine/crates/ze_ecs/Cargo.toml index 1bf1d01..3edf4f1 100644 --- a/ZeroEngine/crates/ze_ecs/Cargo.toml +++ b/ZeroEngine/crates/ze_ecs/Cargo.toml @@ -3,6 +3,9 @@ name = "ze_ecs" version = "0.1.0" edition = "2024" +[lints] +workspace = true + [dependencies] shipyard = { version = "0.11.3", features = ["serde1"] } serde = { version = "1", features = ["derive"] } diff --git a/ZeroEngine/crates/ze_ecs/src/components.rs b/ZeroEngine/crates/ze_ecs/src/components.rs index 1514447..953862d 100644 --- a/ZeroEngine/crates/ze_ecs/src/components.rs +++ b/ZeroEngine/crates/ze_ecs/src/components.rs @@ -48,6 +48,7 @@ pub struct Children { #[derive(Component, Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct Inactive; +#[allow(clippy::struct_excessive_bools)] #[derive(Component, Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct RigidBody { pub body_type: RigidBodyType, @@ -77,6 +78,8 @@ pub struct PhysicsSettings { #[schemars(with = "[f32; 2]")] pub gravity: Vec2, pub enable_debug_draw: bool, + #[serde(default = "default_physics_timestep")] + pub physics_timestep: f32, } impl Default for PhysicsSettings { @@ -84,10 +87,13 @@ impl Default for PhysicsSettings { Self { gravity: Vec2::new(0.0, -9.81), enable_debug_draw: false, + physics_timestep: default_physics_timestep(), } } } +fn default_physics_timestep() -> f32 { 1.0 / 70.0 } + impl Default for RigidBody { fn default() -> Self { Self { @@ -107,7 +113,7 @@ impl Default for RigidBody { } } -fn default_true() -> bool { true } +const fn default_true() -> bool { true } #[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)] pub enum RigidBodyType { diff --git a/ZeroEngine/crates/ze_ecs/src/definitions.rs b/ZeroEngine/crates/ze_ecs/src/definitions.rs index c472b47..8244cef 100644 --- a/ZeroEngine/crates/ze_ecs/src/definitions.rs +++ b/ZeroEngine/crates/ze_ecs/src/definitions.rs @@ -30,7 +30,7 @@ impl From for SavedEntityId { } impl From for EntityId { - fn from(id: SavedEntityId) -> Self { EntityId::new_from_index_and_gen(id.index, id.generation) } + fn from(id: SavedEntityId) -> Self { Self::new_from_index_and_gen(id.index, id.generation) } } #[derive(Debug, Serialize, Deserialize, JsonSchema)] diff --git a/ZeroEngine/crates/ze_ecs/src/entity.rs b/ZeroEngine/crates/ze_ecs/src/entity.rs index d5ad50c..c6690ec 100644 --- a/ZeroEngine/crates/ze_ecs/src/entity.rs +++ b/ZeroEngine/crates/ze_ecs/src/entity.rs @@ -8,9 +8,9 @@ pub struct Entity<'a> { } impl<'a> Entity<'a> { - pub(crate) fn new(id: EntityId, scene: &'a mut crate::Scene) -> Self { Self { id, scene } } + pub(crate) const fn new(id: EntityId, scene: &'a mut crate::Scene) -> Self { Self { id, scene } } - pub fn id(&self) -> EntityId { self.id } + pub const fn id(&self) -> EntityId { self.id } pub fn add_component(&mut self, component: T) -> &mut Self where diff --git a/ZeroEngine/crates/ze_ecs/src/lib.rs b/ZeroEngine/crates/ze_ecs/src/lib.rs index 9f3dab0..41fdc1f 100644 --- a/ZeroEngine/crates/ze_ecs/src/lib.rs +++ b/ZeroEngine/crates/ze_ecs/src/lib.rs @@ -41,7 +41,7 @@ pub fn test() -> Result<()> { let scene_directory = "assets/scenes"; Scene::write_schema(schema_directory.into())?; - scene.save(scene_directory.into(), "main")?; + scene.save(&scene_directory.into(), "main")?; let loaded_scene = Scene::from_name(scene_directory.into(), "main")?; diff --git a/ZeroEngine/crates/ze_ecs/src/registry.rs b/ZeroEngine/crates/ze_ecs/src/registry.rs index 4bf691c..4ec2422 100644 --- a/ZeroEngine/crates/ze_ecs/src/registry.rs +++ b/ZeroEngine/crates/ze_ecs/src/registry.rs @@ -22,7 +22,7 @@ struct ComponentRegistration { } impl ComponentRegistry { - pub fn new() -> Self { + pub const fn new() -> Self { Self { components: BTreeMap::new(), } diff --git a/ZeroEngine/crates/ze_ecs/src/scene.rs b/ZeroEngine/crates/ze_ecs/src/scene.rs index bd19750..da8a920 100644 --- a/ZeroEngine/crates/ze_ecs/src/scene.rs +++ b/ZeroEngine/crates/ze_ecs/src/scene.rs @@ -52,13 +52,13 @@ impl Scene { } impl Scene { - pub fn world(&self) -> &World { &self.world } + pub const fn world(&self) -> &World { &self.world } - pub fn world_mut(&mut self) -> &mut World { &mut self.world } + pub const fn world_mut(&mut self) -> &mut World { &mut self.world } - pub fn registry(&self) -> &ComponentRegistry { &self.registry } + pub const fn registry(&self) -> &ComponentRegistry { &self.registry } - pub fn registry_mut(&mut self) -> &mut ComponentRegistry { &mut self.registry } + pub const fn registry_mut(&mut self) -> &mut ComponentRegistry { &mut self.registry } } impl Scene { @@ -132,7 +132,7 @@ impl Scene { pub fn destroy_entity(&mut self, entity: EntityId) { self.world.delete_entity(entity); } - pub fn entity_mut(&mut self, entity: EntityId) -> Entity<'_> { Entity::new(entity, self) } + pub const fn entity_mut(&mut self, entity: EntityId) -> Entity<'_> { Entity::new(entity, self) } pub(crate) fn add_component(&mut self, entity: EntityId, component: T) where @@ -153,21 +153,21 @@ impl Scene { let mut local_registry = registry; // ? Self::register_defaults(&mut local_registry); let path = scene_path(directory, name); - Self::from_path_with_registry(path, local_registry) + Self::from_path_with_registry(&path, local_registry) } - pub fn from_path(path: PathBuf) -> Result { + pub fn from_path(path: &PathBuf) -> Result { let registry = ComponentRegistry::new(); Self::from_path_with_registry(path, registry) } - pub fn from_path_with_registry(path: PathBuf, registry: ComponentRegistry) -> Result { - let json_text = fs::read_to_string(&path)?; + pub fn from_path_with_registry(path: &PathBuf, registry: ComponentRegistry) -> Result { + let json_text = fs::read_to_string(path)?; let json_value: serde_json::Value = serde_json::from_str(&json_text)?; let schema = Self::schema_value(); - jsonschema::validate(&schema, &json_value).map_err(|err| anyhow!("Invalid scene file: {}", err))?; + jsonschema::validate(&schema, &json_value).map_err(|err| anyhow!("Invalid scene file: {err}"))?; let save_file: SaveFile = serde_json::from_value(json_value)?; @@ -191,7 +191,7 @@ impl Scene { for component in saved_entity.components { registry .load_component(entity, &mut world, component) - .map_err(|err| anyhow!("Cannot load scene: {:?}", err))?; + .map_err(|err| anyhow!("Cannot load scene: {err:?}"))?; } } @@ -203,10 +203,10 @@ impl Scene { }) } - pub fn save(&self, directory: PathBuf, file_name: &str) -> Result<()> { - fs::create_dir_all(&directory)?; + pub fn save(&self, directory: &PathBuf, file_name: &str) -> Result<()> { + fs::create_dir_all(directory)?; - let path = scene_path(&directory, file_name); + let path = scene_path(directory, file_name); let save_file = self.world.run(|entities: EntitiesView| SaveFile { schema: SCENE_SCHEMA_REF.to_string(), diff --git a/ZeroEngine/crates/ze_input/Cargo.toml b/ZeroEngine/crates/ze_input/Cargo.toml index 4894de6..982aa86 100644 --- a/ZeroEngine/crates/ze_input/Cargo.toml +++ b/ZeroEngine/crates/ze_input/Cargo.toml @@ -7,6 +7,9 @@ authors.workspace = true version.workspace = true repository.workspace = true +[lints] +workspace = true + [features] default = ["winit"] winit = ["dep:winit"] diff --git a/ZeroEngine/crates/ze_input/src/lib.rs b/ZeroEngine/crates/ze_input/src/lib.rs index dd20291..23b2a1c 100644 --- a/ZeroEngine/crates/ze_input/src/lib.rs +++ b/ZeroEngine/crates/ze_input/src/lib.rs @@ -85,11 +85,13 @@ impl Input { // --- Updaters --- - pub fn set_key(&mut self, code: ZKeyCode, state: bool) { self.current_keys[code as usize] = state; } + pub const fn set_key(&mut self, code: ZKeyCode, state: bool) { self.current_keys[code as usize] = state; } - pub fn set_mouse_button(&mut self, button: ZMouseCode, state: bool) { self.current_mouse[button as usize] = state; } + pub const fn set_mouse_button(&mut self, button: ZMouseCode, state: bool) { + self.current_mouse[button as usize] = state; + } - pub fn set_mouse_pos(&mut self, x: f32, y: f32) { self.mouse_pos = Vec2::new(x, y); } + pub const fn set_mouse_pos(&mut self, x: f32, y: f32) { self.mouse_pos = Vec2::new(x, y); } pub fn add_mouse_delta(&mut self, dx: f32, dy: f32) { self.mouse_delta.x += dx; @@ -98,14 +100,14 @@ impl Input { pub fn add_mouse_wheel_delta(&mut self, dy: f32) { self.mouse_wheel_delta += dy; } - pub fn late_update(&mut self) { + pub const fn late_update(&mut self) { self.previous_keys = self.current_keys; self.previous_mouse = self.current_mouse; self.mouse_delta = Vec2::new(0.0, 0.0); self.mouse_wheel_delta = 0.0; } - pub fn reset(&mut self) { + pub const fn reset(&mut self) { self.current_keys = [false; 512]; self.previous_keys = [false; 512]; self.current_mouse = [false; 8]; @@ -117,29 +119,29 @@ impl Input { // Keyboard - fn key_pressed(&self, code: ZKeyCode) -> bool { self.current_keys[code as usize] } + const fn key_pressed(&self, code: ZKeyCode) -> bool { self.current_keys[code as usize] } - fn key_just_pressed(&self, code: ZKeyCode) -> bool { + const fn key_just_pressed(&self, code: ZKeyCode) -> bool { self.current_keys[code as usize] && !self.previous_keys[code as usize] } - fn key_released(&self, key_code: ZKeyCode) -> bool { !self.current_keys[key_code as usize] } + const fn key_released(&self, key_code: ZKeyCode) -> bool { !self.current_keys[key_code as usize] } - fn key_just_released(&self, key_code: ZKeyCode) -> bool { + const fn key_just_released(&self, key_code: ZKeyCode) -> bool { !self.key_pressed(key_code) && self.previous_keys[key_code as usize] } // Mouse - fn is_button_pressed(&self, code: ZMouseCode) -> bool { self.current_mouse[code as usize] } + const fn is_button_pressed(&self, code: ZMouseCode) -> bool { self.current_mouse[code as usize] } - fn is_button_just_pressed(&self, code: ZMouseCode) -> bool { + const fn is_button_just_pressed(&self, code: ZMouseCode) -> bool { self.current_mouse[code as usize] && !self.previous_mouse[code as usize] } - fn is_button_released(&self, mouse_code: ZMouseCode) -> bool { !self.current_mouse[mouse_code as usize] } + const fn is_button_released(&self, mouse_code: ZMouseCode) -> bool { !self.current_mouse[mouse_code as usize] } - fn is_button_just_released(&self, mouse_code: ZMouseCode) -> bool { + const fn is_button_just_released(&self, mouse_code: ZMouseCode) -> bool { !self.is_button_pressed(mouse_code) && self.previous_mouse[mouse_code as usize] } } @@ -151,36 +153,73 @@ impl Input { impl Input { // Keyboard - pub fn is_key_pressed(key: ZKeyCode) -> bool { Self::global().lock().unwrap().key_pressed(key) } + pub fn is_key_pressed(key: ZKeyCode) -> bool { + Self::global() + .lock() + .expect("Failed to get Global input") + .key_pressed(key) + } - pub fn is_key_just_pressed(key: ZKeyCode) -> bool { Self::global().lock().unwrap().key_just_pressed(key) } + pub fn is_key_just_pressed(key: ZKeyCode) -> bool { + Self::global() + .lock() + .expect("Failed to get Global input") + .key_just_pressed(key) + } - pub fn is_key_released(key: ZKeyCode) -> bool { Self::global().lock().unwrap().key_released(key) } + pub fn is_key_released(key: ZKeyCode) -> bool { + Self::global() + .lock() + .expect("Failed to get Global input") + .key_released(key) + } - pub fn is_key_just_released(key: ZKeyCode) -> bool { Self::global().lock().unwrap().key_just_released(key) } + pub fn is_key_just_released(key: ZKeyCode) -> bool { + Self::global() + .lock() + .expect("Failed to get Global input") + .key_just_released(key) + } // Mouse - pub fn get_mouse_pos() -> Vec2 { Self::global().lock().unwrap().mouse_pos } + pub fn get_mouse_pos() -> Vec2 { Self::global().lock().expect("Failed to get Global input").mouse_pos } - pub fn get_mouse_delta() -> Vec2 { Self::global().lock().unwrap().mouse_delta } + pub fn get_mouse_delta() -> Vec2 { Self::global().lock().expect("Failed to get Global input").mouse_delta } - pub fn get_mouse_wheel_delta() -> f32 { Self::global().lock().unwrap().mouse_wheel_delta } + pub fn get_mouse_wheel_delta() -> f32 { + Self::global() + .lock() + .expect("Failed to get Global input") + .mouse_wheel_delta + } pub fn is_mouse_button_pressed(button: ZMouseCode) -> bool { - Self::global().lock().unwrap().is_button_pressed(button) + Self::global() + .lock() + .expect("Failed to get Global input") + .is_button_pressed(button) } pub fn is_mouse_button_just_pressed(button: ZMouseCode) -> bool { - Self::global().lock().unwrap().is_button_just_pressed(button) + Self::global() + .lock() + .expect("Failed to get Global input") + .is_button_just_pressed(button) } pub fn is_mouse_button_released(button: ZMouseCode) -> bool { - Self::global().lock().unwrap().is_button_released(button) + Self::global() + .lock() + .expect("Failed to get Global input") + .is_button_released(button) } pub fn is_mouse_button_just_released(button: ZMouseCode) -> bool { - Self::global().lock().unwrap().is_button_just_released(button) + Self::global() + .lock() + .expect("Failed to get Global input") + .is_button_just_released(button) } } @@ -205,13 +244,13 @@ use ze_core::Vec2; static INPUT_INSTANCE: OnceLock> = OnceLock::new(); impl Input { - pub fn global() -> &'static Mutex { INPUT_INSTANCE.get_or_init(|| Mutex::new(Input::new())) } + pub fn global() -> &'static Mutex { INPUT_INSTANCE.get_or_init(|| Mutex::new(Self::new())) } pub fn update_globally(f: F) where - F: FnOnce(&mut Input), + F: FnOnce(&mut Self), { - let mut input = Self::global().lock().unwrap(); + let mut input = Self::global().lock().expect("Failed to get Global input"); f(&mut input); } } @@ -291,12 +330,12 @@ impl_from_winit_keycode! { impl From for ZMouseCode { fn from(button: winit::event::MouseButton) -> Self { match button { - winit::event::MouseButton::Left => ZMouseCode::Left, - winit::event::MouseButton::Right => ZMouseCode::Right, - winit::event::MouseButton::Middle => ZMouseCode::Middle, - winit::event::MouseButton::Back => ZMouseCode::Back, - winit::event::MouseButton::Forward => ZMouseCode::Forward, - winit::event::MouseButton::Other(_) => ZMouseCode::Other, + winit::event::MouseButton::Left => Self::Left, + winit::event::MouseButton::Right => Self::Right, + winit::event::MouseButton::Middle => Self::Middle, + winit::event::MouseButton::Back => Self::Back, + winit::event::MouseButton::Forward => Self::Forward, + winit::event::MouseButton::Other(_) => Self::Other, } } } diff --git a/ZeroEngine/crates/ze_log/Cargo.toml b/ZeroEngine/crates/ze_log/Cargo.toml index 19183b2..a355789 100644 --- a/ZeroEngine/crates/ze_log/Cargo.toml +++ b/ZeroEngine/crates/ze_log/Cargo.toml @@ -7,6 +7,13 @@ authors.workspace = true version.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] -tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "time"] } +tracing-subscriber = { version = "0.3", features = [ + "env-filter", + "fmt", + "time", +] } tracing = "0.1" diff --git a/ZeroEngine/crates/ze_physics/Cargo.toml b/ZeroEngine/crates/ze_physics/Cargo.toml index cab49a1..aa9af6f 100644 --- a/ZeroEngine/crates/ze_physics/Cargo.toml +++ b/ZeroEngine/crates/ze_physics/Cargo.toml @@ -3,7 +3,11 @@ name = "ze_physics" version = "0.1.0" edition = "2024" +[lints] +workspace = true + [dependencies] rapier2d = "0.29" ze_core = { version = "0.1.0", path = "../ze_core" } ze_ecs = { version = "0.1.0", path = "../ze_ecs" } +ze_scripting_cs = { version = "0.1.0", path = "../ze_scripting_cs" } diff --git a/ZeroEngine/crates/ze_physics/src/lib.rs b/ZeroEngine/crates/ze_physics/src/lib.rs index c1b4daa..29e24c2 100644 --- a/ZeroEngine/crates/ze_physics/src/lib.rs +++ b/ZeroEngine/crates/ze_physics/src/lib.rs @@ -1,4 +1,7 @@ -use std::collections::HashMap; +use std::{ + collections::{HashMap, HashSet}, + sync::mpsc::{Receiver, Sender, channel}, +}; use rapier2d::prelude::*; use ze_core::{Quat, Result, Vec2}; @@ -6,8 +9,12 @@ use ze_ecs::{ Collider, ColliderShape, CollisionDetection, EntitiesView, EntityId, PhysicsSettings, RigidBody, RigidBodyType, Scene, System, Transform, }; +use ze_scripting_cs::{ + ScriptingApiCommand, ScriptingRuntimeHandle, drain_scripting_api_commands, refresh_scripting_api_velocity_cache, +}; pub const DEFAULT_GRAVITY: Vec2 = Vec2::new(0.0, -9.81); +pub const DEFAULT_PHYSICS_TIMESTEP: f32 = 1.0 / 70.0; pub struct PhysicsWorld { pipeline: PhysicsPipeline, @@ -23,6 +30,13 @@ pub struct PhysicsWorld { ccd_solver: CCDSolver, entity_bodies: HashMap, entity_colliders: HashMap, + collider_entities: HashMap, + active_contact_pairs: HashSet, + active_sensor_pairs: HashSet, + collision_event_sender: Sender, + collision_event_receiver: Receiver, + contact_force_event_sender: Sender, + contact_force_event_receiver: Receiver, } #[derive(Debug, Clone, Copy)] @@ -31,8 +45,13 @@ struct PhysicsBodyEntry { body_type: RigidBodyType, } +type ColliderPairKey = (ColliderHandle, ColliderHandle); + impl PhysicsWorld { pub fn new() -> Self { + let (collision_event_sender, collision_event_receiver) = channel(); + let (contact_force_event_sender, contact_force_event_receiver) = channel(); + Self { pipeline: PhysicsPipeline::new(), gravity: vector![DEFAULT_GRAVITY.x, DEFAULT_GRAVITY.y], @@ -47,6 +66,13 @@ impl PhysicsWorld { ccd_solver: CCDSolver::new(), entity_bodies: HashMap::new(), entity_colliders: HashMap::new(), + collider_entities: HashMap::new(), + active_contact_pairs: HashSet::new(), + active_sensor_pairs: HashSet::new(), + collision_event_sender, + collision_event_receiver, + contact_force_event_sender, + contact_force_event_receiver, } } @@ -58,11 +84,50 @@ impl PhysicsWorld { pub fn gravity(&self) -> Vec2 { Vec2::new(self.gravity.x, self.gravity.y) } - pub fn set_gravity(&mut self, gravity: Vec2) { self.gravity = vector![gravity.x, gravity.y]; } + pub const fn set_gravity(&mut self, gravity: Vec2) { self.gravity = vector![gravity.x, gravity.y]; } + + pub fn add_2d_force(&mut self, entity: EntityId, force: Vec2) { + let Some(entry) = self.entity_bodies.get(&entity).copied() else { + return; + }; + let Some(body) = self.rigid_bodies.get_mut(entry.handle) else { + return; + }; + + body.add_force(vector![force.x, force.y], true); + } + + pub fn add_2d_impulse(&mut self, entity: EntityId, impulse: Vec2) { + let Some(entry) = self.entity_bodies.get(&entity).copied() else { + return; + }; + let Some(body) = self.rigid_bodies.get_mut(entry.handle) else { + return; + }; + + body.apply_impulse(vector![impulse.x, impulse.y], true); + } + + pub fn body_velocities(&self) -> Vec<(EntityId, f32, f32)> { + self.entity_bodies + .iter() + .filter_map(|(entity, entry)| { + self.rigid_bodies.get(entry.handle).map(|body| { + let velocity = body.linvel(); + (*entity, velocity.x, velocity.y) + }) + }) + .collect() + } - pub fn step(&mut self, dt: f32) { + pub fn step(&mut self, dt: f32) -> Vec { self.integration_parameters.dt = dt.max(0.0); + let event_handler = ChannelEventCollector::new( + self.collision_event_sender.clone(), + self.contact_force_event_sender.clone(), + ); + self.pipeline.step( &self.gravity, &self.integration_parameters, @@ -75,8 +140,11 @@ impl PhysicsWorld { &mut self.multibody_joints, &mut self.ccd_solver, &(), - &(), + &event_handler, ); + + self.contact_force_event_receiver.try_iter().for_each(drop); + self.collision_event_receiver.try_iter().collect() } pub fn register_entity( @@ -119,6 +187,17 @@ impl PhysicsWorld { }, ); self.entity_colliders.insert(entity, collider_handle); + self.collider_entities.insert(collider_handle, entity); + } + + pub fn entity_for_collider(&self, collider: ColliderHandle) -> Option { + self.collider_entities.get(&collider).copied() + } + + pub fn collider_is_sensor(&self, collider: ColliderHandle) -> bool { + self.colliders + .get(collider) + .is_some_and(rapier2d::geometry::Collider::is_sensor) } pub fn sync_from_ecs(&mut self, scene: &Scene) { @@ -178,6 +257,8 @@ impl Default for PhysicsWorld { pub struct PhysicsSystem { world: PhysicsWorld, initialized: bool, + accumulator: f32, + scripting: Option, } impl PhysicsSystem { @@ -185,12 +266,23 @@ impl PhysicsSystem { Self { world: PhysicsWorld::new(), initialized: false, + accumulator: 0.0, + scripting: None, } } - pub fn world(&self) -> &PhysicsWorld { &self.world } + pub fn with_scripting(scripting: ScriptingRuntimeHandle) -> Self { + Self { + world: PhysicsWorld::new(), + initialized: false, + accumulator: 0.0, + scripting: Some(scripting), + } + } - pub fn world_mut(&mut self) -> &mut PhysicsWorld { &mut self.world } + pub const fn world(&self) -> &PhysicsWorld { &self.world } + + pub const fn world_mut(&mut self) -> &mut PhysicsWorld { &mut self.world } fn register_scene_bodies(&mut self, scene: &Scene) { let ecs_world = scene.world(); @@ -227,34 +319,286 @@ impl System for PhysicsSystem { self.initialized = true; } - self.world.set_gravity(scene_gravity(scene)); - self.world.sync_from_ecs(scene); - self.world.step(dt); - self.world.sync_to_ecs(scene) + let settings = scene_physics_settings(scene); + let fixed_dt = settings.physics_timestep.max(f32::EPSILON); + + self.accumulator += dt.max(0.0); + let steps = (self.accumulator / fixed_dt) as usize; + for _ in 0..steps { + self.world.set_gravity(settings.gravity); + self.apply_scripting_api_commands(); + self.world.sync_from_ecs(scene); + let collision_events = self.world.step(fixed_dt); + self.world.sync_to_ecs(scene)?; + + if let Some(scripting) = self.scripting.clone() { + refresh_scripting_api_velocity_cache(self.world.body_velocities()); + scripting.fixed_update(scene, fixed_dt)?; + self.apply_scripting_api_commands(); + self.dispatch_script_collision_events(&scripting, collision_events); + self.apply_scripting_api_commands(); + } + + self.accumulator -= fixed_dt; + } + + Ok(()) } fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } } -fn scene_gravity(scene: &Scene) -> Vec2 { +impl PhysicsSystem { + fn apply_scripting_api_commands(&mut self) { + for command in drain_scripting_api_commands() { + match command { + ScriptingApiCommand::Add2DForce { entity, x, y } => { + self.world.add_2d_force(entity, Vec2::new(x, y)); + } + ScriptingApiCommand::Add2DImpulse { entity, x, y } => { + self.world.add_2d_impulse(entity, Vec2::new(x, y)); + } + } + } + } + + fn dispatch_script_collision_events( + &mut self, + scripting: &ScriptingRuntimeHandle, + collision_events: Vec, + ) { + let previous_contact_pairs = self.world.active_contact_pairs.clone(); + let previous_sensor_pairs = self.world.active_sensor_pairs.clone(); + let mut contact_events = Vec::new(); + let mut sensor_events = Vec::new(); + + for event in collision_events { + if event.sensor() { + sensor_events.push(event); + } else { + contact_events.push(event); + } + } + + for event in contact_events { + self.dispatch_contact_event(scripting, event.collider1(), event.collider2(), event.started()); + } + + let contact_stays = self + .world + .active_contact_pairs + .iter() + .copied() + .filter(|pair| previous_contact_pairs.contains(pair)) + .collect::>(); + + for (collider1, collider2) in contact_stays { + self.dispatch_contact_stay(scripting, collider1, collider2); + } + + for event in sensor_events { + self.dispatch_sensor_event(scripting, event.collider1(), event.collider2(), event.started()); + } + + let sensor_stays = self + .world + .active_sensor_pairs + .iter() + .copied() + .filter(|pair| previous_sensor_pairs.contains(pair)) + .collect::>(); + + for (collider1, collider2) in sensor_stays { + self.dispatch_sensor_stay(scripting, collider1, collider2); + } + } + + fn dispatch_contact_event( + &mut self, + scripting: &ScriptingRuntimeHandle, + collider1: ColliderHandle, + collider2: ColliderHandle, + started: bool, + ) { + let Some((entity1, entity2)) = self.entities_for_pair(collider1, collider2) else { + return; + }; + + let pair = collider_pair_key(collider1, collider2); + + if started { + self.world.active_contact_pairs.insert(pair); + scripting.on_contact_enter(entity1, entity2); + scripting.on_contact_enter(entity2, entity1); + } else { + self.world.active_contact_pairs.remove(&pair); + scripting.on_contact_exit(entity1, entity2); + scripting.on_contact_exit(entity2, entity1); + } + } + + fn dispatch_contact_stay( + &self, + scripting: &ScriptingRuntimeHandle, + collider1: ColliderHandle, + collider2: ColliderHandle, + ) { + let Some((entity1, entity2)) = self.entities_for_pair(collider1, collider2) else { + return; + }; + + scripting.on_contact_stay(entity1, entity2); + scripting.on_contact_stay(entity2, entity1); + } + + fn dispatch_sensor_event( + &mut self, + scripting: &ScriptingRuntimeHandle, + collider1: ColliderHandle, + collider2: ColliderHandle, + started: bool, + ) { + let pair = collider_pair_key(collider1, collider2); + + if started { + self.world.active_sensor_pairs.insert(pair); + self.dispatch_sensor_enter(scripting, collider1, collider2); + } else { + self.world.active_sensor_pairs.remove(&pair); + self.dispatch_sensor_exit(scripting, collider1, collider2); + } + } + + fn dispatch_sensor_stay( + &self, + scripting: &ScriptingRuntimeHandle, + collider1: ColliderHandle, + collider2: ColliderHandle, + ) { + self.dispatch_sensor_callbacks( + scripting, + collider1, + collider2, + ScriptingRuntimeHandle::on_contact_stay, + ScriptingRuntimeHandle::on_sensor_stay, + ); + } + + fn dispatch_sensor_enter( + &self, + scripting: &ScriptingRuntimeHandle, + collider1: ColliderHandle, + collider2: ColliderHandle, + ) { + self.dispatch_sensor_callbacks( + scripting, + collider1, + collider2, + ScriptingRuntimeHandle::on_contact_enter, + ScriptingRuntimeHandle::on_sensor_enter, + ); + } + + fn dispatch_sensor_exit( + &self, + scripting: &ScriptingRuntimeHandle, + collider1: ColliderHandle, + collider2: ColliderHandle, + ) { + self.dispatch_sensor_callbacks( + scripting, + collider1, + collider2, + ScriptingRuntimeHandle::on_contact_exit, + ScriptingRuntimeHandle::on_sensor_exit, + ); + } + + fn dispatch_sensor_callbacks( + &self, + scripting: &ScriptingRuntimeHandle, + collider1: ColliderHandle, + collider2: ColliderHandle, + contact_callback: fn(&ScriptingRuntimeHandle, EntityId, EntityId), + sensor_callback: fn(&ScriptingRuntimeHandle, EntityId, EntityId), + ) { + let Some((entity1, entity2)) = self.entities_for_pair(collider1, collider2) else { + return; + }; + + let collider1_is_sensor = self.world.collider_is_sensor(collider1); + let collider2_is_sensor = self.world.collider_is_sensor(collider2); + + match (collider1_is_sensor, collider2_is_sensor) { + (true, false) => { + contact_callback(scripting, entity1, entity2); + sensor_callback(scripting, entity2, entity1); + } + (false, true) => { + contact_callback(scripting, entity2, entity1); + sensor_callback(scripting, entity1, entity2); + } + (true, true) => { + contact_callback(scripting, entity1, entity2); + contact_callback(scripting, entity2, entity1); + sensor_callback(scripting, entity1, entity2); + sensor_callback(scripting, entity2, entity1); + } + (false, false) => {} + } + } + + fn entities_for_pair(&self, collider1: ColliderHandle, collider2: ColliderHandle) -> Option<(EntityId, EntityId)> { + Some(( + self.world.entity_for_collider(collider1)?, + self.world.entity_for_collider(collider2)?, + )) + } +} + +fn collider_pair_key(collider1: ColliderHandle, collider2: ColliderHandle) -> ColliderPairKey { + if collider1.into_raw_parts() <= collider2.into_raw_parts() { + (collider1, collider2) + } else { + (collider2, collider1) + } +} + +fn scene_physics_settings(scene: &Scene) -> PhysicsStepSettings { let world = scene.world(); - let mut gravity = DEFAULT_GRAVITY; + let mut settings = PhysicsStepSettings::default(); world.run(|entities: EntitiesView| { for entity in entities.iter() { - let Ok(settings) = world.get::<&PhysicsSettings>(entity) else { + let Ok(physics_settings) = world.get::<&PhysicsSettings>(entity) else { continue; }; - gravity = settings.gravity; + settings.gravity = physics_settings.gravity; + settings.physics_timestep = physics_settings.physics_timestep; break; } }); - gravity + settings +} + +#[derive(Clone, Copy)] +struct PhysicsStepSettings { + gravity: Vec2, + physics_timestep: f32, +} + +impl Default for PhysicsStepSettings { + fn default() -> Self { + Self { + gravity: DEFAULT_GRAVITY, + physics_timestep: DEFAULT_PHYSICS_TIMESTEP, + } + } } -fn to_rapier_body_type(body_type: RigidBodyType) -> rapier2d::dynamics::RigidBodyType { +const fn to_rapier_body_type(body_type: RigidBodyType) -> rapier2d::dynamics::RigidBodyType { match body_type { RigidBodyType::Static => rapier2d::dynamics::RigidBodyType::Fixed, RigidBodyType::Dynamic => rapier2d::dynamics::RigidBodyType::Dynamic, @@ -263,7 +607,7 @@ fn to_rapier_body_type(body_type: RigidBodyType) -> rapier2d::dynamics::RigidBod } } -fn is_kinematic(body_type: RigidBodyType) -> bool { +const fn is_kinematic(body_type: RigidBodyType) -> bool { matches!( body_type, RigidBodyType::KinematicPositionBased | RigidBodyType::KinematicVelocityBased @@ -306,7 +650,8 @@ fn build_collider(collider: &Collider, mass: Option, scale: Vec2) -> rapier let builder = builder .restitution(collider.restitution) .friction(collider.friction) - .sensor(collider.is_sensor); + .sensor(collider.is_sensor) + .active_events(ActiveEvents::COLLISION_EVENTS); let builder = if let Some(mass) = mass { builder.mass(mass) diff --git a/ZeroEngine/crates/ze_renderer/Cargo.toml b/ZeroEngine/crates/ze_renderer/Cargo.toml index a04fd7f..38b25a9 100644 --- a/ZeroEngine/crates/ze_renderer/Cargo.toml +++ b/ZeroEngine/crates/ze_renderer/Cargo.toml @@ -3,6 +3,9 @@ name = "ze_renderer" version = "0.1.0" edition = "2024" +[lints] +workspace = true + [dependencies] image = { version = "0.25.10", default-features = false, features = [ "webp", diff --git a/ZeroEngine/crates/ze_renderer/src/backend/bind_group.rs b/ZeroEngine/crates/ze_renderer/src/backend/bind_group.rs index 56907b3..7a8d7c9 100644 --- a/ZeroEngine/crates/ze_renderer/src/backend/bind_group.rs +++ b/ZeroEngine/crates/ze_renderer/src/backend/bind_group.rs @@ -7,7 +7,7 @@ pub struct Builder<'a> { } impl<'a> Builder<'a> { - pub fn new(device: &'a wgpu::Device) -> Self { + pub const fn new(device: &'a wgpu::Device) -> Self { // TODO: replace &'a to pointer Self { entries: Vec::new(), @@ -16,7 +16,7 @@ impl<'a> Builder<'a> { } } - pub fn set_layout(&mut self, layout: &'a wgpu::BindGroupLayout) { self.layout = Some(layout); } + pub const fn set_layout(&mut self, layout: &'a wgpu::BindGroupLayout) { self.layout = Some(layout); } fn reset(&mut self) { self.entries.clear(); } @@ -34,7 +34,8 @@ impl<'a> Builder<'a> { self.add_buffer( buffer, 0, - NonZeroU64::new(std::mem::size_of::() as u64).unwrap(), + NonZeroU64::new(std::mem::size_of::() as u64) + .expect("size of SpriteMaterialUniform is zero"), ); } @@ -52,7 +53,7 @@ impl<'a> Builder<'a> { pub fn build(&mut self, label: &str) -> wgpu::BindGroup { let descriptor = wgpu::BindGroupDescriptor { label: Some(label), - layout: self.layout.unwrap(), + layout: self.layout.expect("Layout is None..."), entries: &self.entries, }; diff --git a/ZeroEngine/crates/ze_renderer/src/backend/bind_group_layout.rs b/ZeroEngine/crates/ze_renderer/src/backend/bind_group_layout.rs index 6808d01..d12b279 100644 --- a/ZeroEngine/crates/ze_renderer/src/backend/bind_group_layout.rs +++ b/ZeroEngine/crates/ze_renderer/src/backend/bind_group_layout.rs @@ -4,7 +4,7 @@ pub struct Builder<'a> { } impl<'a> Builder<'a> { - pub fn new(device: &'a wgpu::Device) -> Self { + pub const fn new(device: &'a wgpu::Device) -> Self { // TODO: replace &'a to pointer Self { entries: Vec::new(), diff --git a/ZeroEngine/crates/ze_renderer/src/backend/mesh.rs b/ZeroEngine/crates/ze_renderer/src/backend/mesh.rs index 6443c1f..8aa51a9 100644 --- a/ZeroEngine/crates/ze_renderer/src/backend/mesh.rs +++ b/ZeroEngine/crates/ze_renderer/src/backend/mesh.rs @@ -1,5 +1,5 @@ use bytemuck::{Pod, Zeroable}; -use glam::*; +use glam::{Vec3, Vec4, f32, u8, u16, u64}; use wgpu::util::DeviceExt; pub struct Mesh { @@ -15,31 +15,31 @@ pub struct Vertex { } impl Vertex { - pub fn get_layout() -> wgpu::VertexBufferLayout<'static> { + pub const fn get_layout() -> wgpu::VertexBufferLayout<'static> { const ATTRIBUTES: [wgpu::VertexAttribute; 2] = wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x4]; wgpu::VertexBufferLayout { - array_stride: std::mem::size_of::() as u64, + array_stride: std::mem::size_of::() as u64, step_mode: wgpu::VertexStepMode::Vertex, attributes: &ATTRIBUTES, } } pub fn make_quad(device: &wgpu::Device) -> Mesh { - let vertices: [Vertex; 4] = [ - Vertex { + let vertices: [Self; 4] = [ + Self { position: Vec3::new(-0.5, -0.5, 0.0).to_array(), color: Vec4::ONE.to_array(), }, - Vertex { + Self { position: Vec3::new(0.5, -0.5, 0.0).to_array(), color: Vec4::ONE.to_array(), }, - Vertex { + Self { position: Vec3::new(0.5, 0.5, 0.0).to_array(), color: Vec4::ONE.to_array(), }, - Vertex { + Self { position: Vec3::new(-0.5, 0.5, 0.0).to_array(), color: Vec4::ONE.to_array(), }, @@ -57,7 +57,7 @@ impl Vertex { }; let buffer = device.create_buffer_init(&buffer_descriptor); - let offset: u64 = bytes_1.len().try_into().unwrap(); + let offset: u64 = bytes_1.len().try_into().expect("Cannon get len of bytes_1 as u64"); Mesh { buffer, offset } } diff --git a/ZeroEngine/crates/ze_renderer/src/backend/pipeline.rs b/ZeroEngine/crates/ze_renderer/src/backend/pipeline.rs index 4dc3199..2d7b9f2 100644 --- a/ZeroEngine/crates/ze_renderer/src/backend/pipeline.rs +++ b/ZeroEngine/crates/ze_renderer/src/backend/pipeline.rs @@ -86,7 +86,7 @@ impl<'a> Builder<'a> { self } - pub fn with_pixel_format(mut self, format: wgpu::TextureFormat) -> Self { + pub const fn with_pixel_format(mut self, format: wgpu::TextureFormat) -> Self { self.pixel_format = format; self } @@ -96,27 +96,27 @@ impl<'a> Builder<'a> { self } - pub fn with_topology(mut self, topology: wgpu::PrimitiveTopology) -> Self { + pub const fn with_topology(mut self, topology: wgpu::PrimitiveTopology) -> Self { self.topology = topology; self } - pub fn with_polygon_mode(mut self, polygon_mode: wgpu::PolygonMode) -> Self { + pub const fn with_polygon_mode(mut self, polygon_mode: wgpu::PolygonMode) -> Self { self.polygon_mode = polygon_mode; self } - pub fn with_cull_mode(mut self, cull_mode: Option) -> Self { + pub const fn with_cull_mode(mut self, cull_mode: Option) -> Self { self.cull_mode = cull_mode; self } - pub fn with_depth_write_enabled(mut self, enabled: bool) -> Self { + pub const fn with_depth_write_enabled(mut self, enabled: bool) -> Self { self.depth_write_enabled = enabled; self } - pub fn with_depth_compare(mut self, compare: wgpu::CompareFunction) -> Self { + pub const fn with_depth_compare(mut self, compare: wgpu::CompareFunction) -> Self { self.depth_compare = compare; self } @@ -136,7 +136,7 @@ impl<'a> Builder<'a> { } Some(ShaderSource::Source(source)) => source, None => { - ze_core::bail!("Pipeline `{}` has no shader source", self.name.clone()); + ze_core::bail!("Pipeline `{}` has no shader source", self.name); } }; diff --git a/ZeroEngine/crates/ze_renderer/src/backend/texture.rs b/ZeroEngine/crates/ze_renderer/src/backend/texture.rs index 33f50ad..864361c 100644 --- a/ZeroEngine/crates/ze_renderer/src/backend/texture.rs +++ b/ZeroEngine/crates/ze_renderer/src/backend/texture.rs @@ -51,7 +51,7 @@ impl TextureResource { width: size.0, height: size.1, }; - Ok(Self::from_rgba(source, device, queue)) + Ok(Self::from_rgba(&source, device, queue)) } pub fn fallback(device: &wgpu::Device, queue: &wgpu::Queue) -> Self { @@ -62,10 +62,10 @@ impl TextureResource { width: 2, height: 2, }; - Self::from_rgba(source, device, queue) + Self::from_rgba(&source, device, queue) } - fn from_rgba(source: RgbaTextureSource<'_>, device: &wgpu::Device, queue: &wgpu::Queue) -> Self { + fn from_rgba(source: &RgbaTextureSource<'_>, device: &wgpu::Device, queue: &wgpu::Queue) -> Self { let texture_size = wgpu::Extent3d { width: source.width, height: source.height, @@ -119,7 +119,7 @@ impl TextureResource { } } - pub fn dimensions(&self) -> (u32, u32) { (self.width, self.height) } + pub const fn dimensions(&self) -> (u32, u32) { (self.width, self.height) } } impl SpriteMaterial { diff --git a/ZeroEngine/crates/ze_renderer/src/backend/ubo.rs b/ZeroEngine/crates/ze_renderer/src/backend/ubo.rs index ff4e11f..1d84b1b 100644 --- a/ZeroEngine/crates/ze_renderer/src/backend/ubo.rs +++ b/ZeroEngine/crates/ze_renderer/src/backend/ubo.rs @@ -10,10 +10,10 @@ pub struct UboGroup { impl UboGroup { pub fn new(device: &wgpu::Device, object_count: usize, layout: &wgpu::BindGroupLayout) -> Self { - fn align_to(value: u64, alignment: u64) -> u64 { value.div_ceil(alignment) * alignment } + const fn align_to(value: u64, alignment: u64) -> u64 { value.div_ceil(alignment) * alignment } let stride = align_to( std::mem::size_of::() as u64, - device.limits().min_uniform_buffer_offset_alignment as u64, + u64::from(device.limits().min_uniform_buffer_offset_alignment), ); let buffer_descriptor = wgpu::BufferDescriptor { @@ -32,7 +32,7 @@ impl UboGroup { builder.add_buffer( &buffer, i as u64 * stride, - NonZeroU64::new(std::mem::size_of::() as u64).unwrap(), + NonZeroU64::new(std::mem::size_of::() as u64).expect("size of glam::Mat4 is zero"), ); bind_groups.push(builder.build("Matrix")); } @@ -44,7 +44,7 @@ impl UboGroup { } } - pub fn upload(&mut self, i: u64, matrix: &glam::Mat4, queue: &wgpu::Queue) { + pub fn upload(&self, i: u64, matrix: &glam::Mat4, queue: &wgpu::Queue) { let offset = i * self.allignment; let data = bytemuck::bytes_of(matrix); queue.write_buffer(&self.buffer, offset, data); @@ -74,7 +74,7 @@ impl Ubo { builder.add_buffer( &buffer, 0, - NonZeroU64::new(std::mem::size_of::() as u64).unwrap(), + NonZeroU64::new(std::mem::size_of::() as u64).expect("size of glam::Mat4 is zero"), ); bind_group = builder.build("Matrix"); } @@ -82,7 +82,7 @@ impl Ubo { Self { buffer, bind_group } } - pub fn upload(&mut self, matrix: &glam::Mat4, queue: &wgpu::Queue) { + pub fn upload(&self, matrix: &glam::Mat4, queue: &wgpu::Queue) { let data = bytemuck::bytes_of(matrix); queue.write_buffer(&self.buffer, 0, data); } diff --git a/ZeroEngine/crates/ze_renderer/src/editor_camera_system.rs b/ZeroEngine/crates/ze_renderer/src/editor_camera_system.rs index acad2a5..58b6428 100644 --- a/ZeroEngine/crates/ze_renderer/src/editor_camera_system.rs +++ b/ZeroEngine/crates/ze_renderer/src/editor_camera_system.rs @@ -11,7 +11,7 @@ pub struct EditorCameraSystem { } impl EditorCameraSystem { - pub fn new() -> Self { + pub const fn new() -> Self { Self { speed: 5.0, fast_speed: 10.0, @@ -94,7 +94,7 @@ impl System for EditorCameraSystem { let mut camera = scene.world_mut().get::<&mut Camera>(entity)?; match &mut camera.projection { CameraProjection::Orthographic { size, .. } => { - let zoom_factor = (1.0 - wheel_delta * self.zoom_step).clamp(0.2, 5.0); + let zoom_factor = wheel_delta.mul_add(-self.zoom_step, 1.0).clamp(0.2, 5.0); *size = (*size * zoom_factor).clamp(0.1, 1000.0); } CameraProjection::Perspective { fov_y_radians, .. } => { diff --git a/ZeroEngine/crates/ze_renderer/src/lib.rs b/ZeroEngine/crates/ze_renderer/src/lib.rs index 534cd5d..58da77b 100644 --- a/ZeroEngine/crates/ze_renderer/src/lib.rs +++ b/ZeroEngine/crates/ze_renderer/src/lib.rs @@ -12,10 +12,10 @@ use wgpu::util::DeviceExt; use ze_core::{Mat4, ResourceManager, Vec3}; use crate::backend::{ - mesh::*, - pipeline::{self, *}, + mesh::{Mesh, Vertex}, + pipeline::{self, Pipeline}, texture::{SpriteMaterial, SpriteMaterialUniform, TextureCache}, - ubo::*, + ubo::{Ubo, UboGroup}, }; pub struct Renderer { @@ -42,7 +42,7 @@ impl Renderer { pub async fn new(window: Arc) -> ze_core::Result { let instance = wgpu::Instance::default(); - let surface = instance.create_surface(window.clone()).unwrap(); + let surface = instance.create_surface(window.clone())?; let adapter_descriptor = wgpu::RequestAdapterOptionsBase { power_preference: wgpu::PowerPreference::HighPerformance, @@ -50,11 +50,16 @@ impl Renderer { force_fallback_adapter: false, }; - let adapter = instance.request_adapter(&adapter_descriptor).await.unwrap(); + let adapter = instance.request_adapter(&adapter_descriptor).await?; let info = adapter.get_info(); - ze_log::info!("Renderer: {} ({:?})", info.name, info.backend); + // ze_log::info!("Renderer: {} ({:?})", info.name, info.backend); + + println!("Renderer:"); + println!("-> Vendor: {}", info.vendor); + println!("-> Name: {}", info.name); + println!("-> Driver: {} {}", info.driver, info.driver_info); let device_descriptor = wgpu::DeviceDescriptor { required_features: wgpu::Features::POLYGON_MODE_LINE, @@ -65,7 +70,7 @@ impl Renderer { experimental_features: wgpu::ExperimentalFeatures::disabled(), }; - let (device, queue) = adapter.request_device(&device_descriptor).await.unwrap(); + let (device, queue) = adapter.request_device(&device_descriptor).await?; let size = window.inner_size(); @@ -74,7 +79,7 @@ impl Renderer { .formats .iter() .copied() - .find(|f| f.is_srgb()) + .find(wgpu::TextureFormat::is_srgb) .unwrap_or(surface_capabilities.formats[0]); let config = wgpu::SurfaceConfiguration { @@ -221,20 +226,13 @@ impl Renderer { camera: &render_system::CameraRenderData, resources: &ResourceManager, ) { - match self.render_sprite_items(items, debug_lines, camera, resources) { - Ok(_) => {} - Err(wgpu::SurfaceStatus::Lost) => { - let size = self.size; - self.resize(size); - } - Err(e) => ze_log::error!("Render error: {:?}", e), - } + self.render_sprite_items(items, debug_lines, camera, resources); } fn update_projection(&mut self, camera: &render_system::CameraRenderData) { self.projection_ubo .as_mut() - .unwrap() + .expect("Cannon get projection_ubo as mut :(") .upload(&camera.view_projection, &self.queue); } @@ -244,7 +242,7 @@ impl Renderer { debug_lines: &[render_system::DebugLine], camera: &render_system::CameraRenderData, resources: &ResourceManager, - ) -> Result<(), wgpu::SurfaceStatus> { + ) { self.ensure_ubos_for_objects(items.len()); self.update_projection(camera); @@ -259,7 +257,10 @@ impl Renderer { let sprite_size = sprite_size_to_world_scale(&item.size, texture.dimensions()); let transform = item.transform * Mat4::from_scale(Vec3::new(sprite_size[0], sprite_size[1], 1.0)); - self.ubo.as_mut().unwrap().upload(i as u64, &transform, &self.queue); + self.ubo + .as_mut() + .expect("Cannon get ubo as mut ):") + .upload(i as u64, &transform, &self.queue); let tint = item.color.tint.unwrap_or([1.0, 1.0, 1.0, 1.0]); let mode = match item.color.mode { @@ -305,11 +306,8 @@ impl Renderer { }); let drawable = self.surface.get_current_texture(); - let drawable = match drawable { - wgpu::CurrentSurfaceTexture::Timeout => return Ok(()), - wgpu::CurrentSurfaceTexture::Lost => return Ok(()), - wgpu::CurrentSurfaceTexture::Success(t) => t, - _ => return Ok(()), + let wgpu::CurrentSurfaceTexture::Success(drawable) = drawable else { + return; }; let image_view_descriptor = wgpu::TextureViewDescriptor::default(); @@ -326,10 +324,10 @@ impl Renderer { depth_slice: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { - r: camera.clear_color[0] as f64, - g: camera.clear_color[1] as f64, - b: camera.clear_color[2] as f64, - a: camera.clear_color[3] as f64, + r: f64::from(camera.clear_color[0]), + g: f64::from(camera.clear_color[1]), + b: f64::from(camera.clear_color[2]), + a: f64::from(camera.clear_color[3]), }), store: wgpu::StoreOp::Store, }, @@ -353,11 +351,23 @@ impl Renderer { let mut render_pass = command_encoder.begin_render_pass(&render_pass_descriptor); render_pass.set_pipeline(&self.pipeline.render_pipeline); - render_pass.set_bind_group(2, &self.projection_ubo.as_ref().unwrap().bind_group, &[]); + render_pass.set_bind_group( + 2, + &self + .projection_ubo + .as_ref() + .expect("Cannon get projection_ubo as ref :(") + .bind_group, + &[], + ); for (i, material) in self.materials.iter().enumerate() { render_pass.set_bind_group(0, &material.bind_group, &[]); - render_pass.set_bind_group(1, &self.ubo.as_ref().unwrap().bind_groups[i], &[]); + render_pass.set_bind_group( + 1, + &self.ubo.as_ref().expect("Cannon get ubo as ref :(").bind_groups[i], + &[], + ); render_pass.set_vertex_buffer(0, self.quad_mesh.buffer.slice(..self.quad_mesh.offset)); render_pass.set_index_buffer( @@ -369,7 +379,15 @@ impl Renderer { if let Some(debug_vertex_buffer) = &debug_vertex_buffer { render_pass.set_pipeline(&self.debug_pipeline.render_pipeline); - render_pass.set_bind_group(0, &self.projection_ubo.as_ref().unwrap().bind_group, &[]); + render_pass.set_bind_group( + 0, + &self + .projection_ubo + .as_ref() + .expect("Cannon get projection_ubo as ref :(") + .bind_group, + &[], + ); render_pass.set_vertex_buffer(0, debug_vertex_buffer.slice(..)); render_pass.draw(0..debug_vertices.len() as u32, 0..1); } @@ -378,12 +396,10 @@ impl Renderer { self.queue.submit(std::iter::once(command_encoder.finish())); drawable.present(); - - Ok(()) } } -const DEBUG_LINE_SHADER: &str = r#" +const DEBUG_LINE_SHADER: &str = r" @group(0) @binding(0) var view_projection: mat4x4; struct Vertex { @@ -408,7 +424,7 @@ fn vs_main(vertex: Vertex) -> VertexOutput { fn fs_main(in: VertexOutput) -> @location(0) vec4 { return in.color; } -"#; +"; fn sprite_size_to_world_scale(size: &components::SpriteSize, dimensions: (u32, u32)) -> [f32; 2] { match size { diff --git a/ZeroEngine/crates/ze_renderer/src/render_system.rs b/ZeroEngine/crates/ze_renderer/src/render_system.rs index 4c3d91e..ce6930c 100644 --- a/ZeroEngine/crates/ze_renderer/src/render_system.rs +++ b/ZeroEngine/crates/ze_renderer/src/render_system.rs @@ -41,20 +41,19 @@ pub struct RenderSystem { impl RenderSystem { pub fn new() -> Self { Self::default() } - pub fn render(&mut self, scene: &Scene, renderer: &mut Renderer, resources: &ResourceManager) -> Result<()> { - self.render_scene(scene, renderer, resources) + pub fn render(&mut self, scene: &Scene, renderer: &mut Renderer, resources: &ResourceManager) { + self.render_scene(scene, renderer, resources); } - fn render_scene(&mut self, scene: &Scene, renderer: &mut Renderer, resources: &ResourceManager) -> Result<()> { + fn render_scene(&mut self, scene: &Scene, renderer: &mut Renderer, resources: &ResourceManager) { let Some(camera) = Self::find_primary_camera(scene, renderer.aspect_ratio()) else { ze_log::warn!("No primary camera found in scene `{}`", scene.name); - return Ok(()); + return; }; self.items = Self::collect_items(scene); self.debug_lines = Self::collect_debug_lines(scene); renderer.request_sprite_redraw(&self.items, &self.debug_lines, &camera, resources); - Ok(()) } fn find_primary_camera(scene: &Scene, aspect: f32) -> Option { @@ -333,12 +332,19 @@ fn capsule_points(half_height: f32, radius: f32, arc_segments: usize) -> Vec "Release", + _ => "Debug", + }; + + let status = Command::new("dotnet") + .arg("build") + .arg(&project_path) + .arg("--configuration") + .arg(configuration) + .arg("--nologo") + .status() + .unwrap_or_else(|error| panic!("failed to run dotnet build for {}: {error}", project_path.display())); + + assert!(status.success(), "dotnet build failed for {}", project_path.display()); +} + +fn workspace_root() -> PathBuf { + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not set")); + + manifest_dir + .ancestors() + .find(|path| path.join("assets/scripts").is_dir() || is_workspace_root(path)) + .unwrap_or_else(|| Path::new(".")) + .to_path_buf() +} + +fn is_workspace_root(path: &Path) -> bool { path.join("Cargo.toml").is_file() && path.join("ZeroEngine").is_dir() } diff --git a/ZeroEngine/crates/ze_scripting_cs/src/lib.rs b/ZeroEngine/crates/ze_scripting_cs/src/lib.rs new file mode 100644 index 0000000..dca780b --- /dev/null +++ b/ZeroEngine/crates/ze_scripting_cs/src/lib.rs @@ -0,0 +1,1069 @@ +use std::{ + any::Any, + cell::RefCell, + collections::HashMap, + env, fs, + path::{Path, PathBuf}, + rc::Rc, +}; + +use fn_ptr::{WithAbi, abi::System as AbiSystem}; +use netcorehost::{ + hostfxr::{ + AssemblyDelegateLoader, FnPtr, GetManagedFunctionError, Hostfxr, HostfxrContext, InitializedForRuntimeConfig, + }, + pdcstr, + pdcstring::{PdCStr, PdCString}, +}; +use ze_core::{Context, Result, anyhow}; +use ze_ecs::{ + Component, Deserialize, EntitiesView, EntityId, JsonSchema, Scene, Serialize, System, registry::ComponentRegistry, +}; + +const ASSEMBLY_PATH: &str = "assets/scripts/bin/Scripts.dll"; +const RUNTIME_CONFIG_PATH: &str = "assets/scripts/bin/Scripts.runtimeconfig.json"; +const SCRIPT_ASSEMBLY_NAME: &str = "Scripts"; +const SCRIPT_HOST_TYPE: &str = "ZeroEngine.ZEScript"; + +pub type ScriptFn = >::F; +pub type ScriptInitFn = >::F; +pub type ScriptEntityFn = >::F; + +#[repr(C)] +pub struct EngineAPI { + pub is_key_pressed: extern "C" fn(i32) -> bool, + pub is_key_just_pressed: extern "C" fn(i32) -> bool, + pub is_key_released: extern "C" fn(i32) -> bool, + pub is_key_just_released: extern "C" fn(i32) -> bool, + pub is_mouse_button_pressed: extern "C" fn(i32) -> bool, + pub is_mouse_button_just_pressed: extern "C" fn(i32) -> bool, + pub get_mouse_position: extern "C" fn(*mut f32, *mut f32), + pub get_mouse_delta: extern "C" fn(*mut f32, *mut f32), + pub get_time_state_ptr: extern "C" fn() -> *const ScriptingTimeState, + pub has_component: extern "C" fn(u64, u32) -> bool, + pub get_velocity: extern "C" fn(u64, *mut f32, *mut f32), + pub add_2d_force: extern "C" fn(u64, f32, f32), + pub add_2d_impulse: extern "C" fn(u64, f32, f32), +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct ScriptingTimeState { + pub delta_time: f32, + pub fixed_delta_time: f32, + pub unscaled_delta_time: f32, + pub time_scale: f32, + pub time_since_startup: f64, + pub unscaled_time_since_startup: f64, + pub fixed_time: f64, + pub frame_count: u64, + pub fixed_frame_count: u64, + pub is_fixed_update: u8, +} + +impl ScriptingTimeState { + const fn initial() -> Self { + Self { + delta_time: 0.0, + fixed_delta_time: 0.0, + unscaled_delta_time: 0.0, + time_scale: 1.0, + time_since_startup: 0.0, + unscaled_time_since_startup: 0.0, + fixed_time: 0.0, + frame_count: 0, + fixed_frame_count: 0, + is_fixed_update: 0, + } + } +} + +static ENGINE_API: EngineAPI = EngineAPI { + is_key_pressed: api::is_key_pressed, + is_key_just_pressed: api::is_key_just_pressed, + is_key_released: api::is_key_released, + is_key_just_released: api::is_key_just_released, + is_mouse_button_pressed: api::is_mouse_button_pressed, + is_mouse_button_just_pressed: api::is_mouse_button_just_pressed, + get_mouse_position: api::get_mouse_position, + get_mouse_delta: api::get_mouse_delta, + get_time_state_ptr: api::get_time_state_ptr, + has_component: api::has_component, + get_velocity: api::get_velocity, + add_2d_force: api::add_2d_force, + add_2d_impulse: api::add_2d_impulse, +}; + +#[derive(Component, Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct Script { + pub path: String, + #[serde(default = "default_enabled")] + pub enabled: bool, +} + +impl Default for Script { + fn default() -> Self { + Self { + path: "Scripts.Script".to_string(), + enabled: true, + } + } +} + +pub fn register_scripting_components(registry: &mut ComponentRegistry) { + registry.register::