diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c181ae1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lyra-scripting/elua"] + path = lyra-scripting/elua + url = git@git.seanomik.net:SeanOMik/elua.git diff --git a/.vscode/launch.json b/.vscode/launch.json index 238c8c0..a05d1f5 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -72,7 +72,7 @@ "--no-run", "--lib", "--package=lyra-ecs", - "world::tests::view_change_tracking", + "command::tests::deferred_commands", "--", "--exact --nocapture" ], diff --git a/Cargo.lock b/Cargo.lock index e2e17a2..53d3f80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,6 +33,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.7" @@ -190,7 +201,7 @@ dependencies = [ "polling 2.8.0", "rustix 0.37.27", "slab", - "socket2", + "socket2 0.4.10", "waker-fn", ] @@ -310,7 +321,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -319,12 +330,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atomicell" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157342dd84c64f16899b4b16c1fb2cce54b887990362aac3c590b3d13810890f" - [[package]] name = "autocfg" version = "1.1.0" @@ -358,6 +363,12 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bit-set" version = "0.5.3" @@ -397,6 +408,15 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block-sys" version = "0.1.0-beta.1" @@ -432,16 +452,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "bstr" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "bumpalo" version = "3.14.0" @@ -465,7 +475,7 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -474,6 +484,33 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "calloop" version = "0.10.6" @@ -510,6 +547,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -541,6 +588,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "core-foundation" version = "0.9.4" @@ -581,6 +634,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.3.2" @@ -590,6 +652,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + [[package]] name = "crossbeam-channel" version = "0.5.10" @@ -622,6 +697,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.18" @@ -637,6 +721,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "d3d12" version = "0.6.0" @@ -657,6 +751,17 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "dispatch" version = "0.2.0" @@ -678,48 +783,43 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "edict" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85bf7cde5687ce04b093bfe183453fa12996b6bdfd2567a0262ebd621761d77" -dependencies = [ - "atomicell", - "edict-proc", - "hashbrown 0.13.2", - "parking_lot", - "smallvec", - "tiny-fn", -] - -[[package]] -name = "edict-proc" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94d80dc0f05250a9082bb9455bbf3d6c6c51db388b060df914aebcfb4a9b9f1" -dependencies = [ - "edict-proc-lib", - "syn 2.0.48", -] - -[[package]] -name = "edict-proc-lib" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d98f9931a4f71c7eb7d85cf4ef1271b27014625c85a65376a52c10ac4ffaea" -dependencies = [ - "proc-easy", - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "elua" +version = "0.1.0" +dependencies = [ + "elua-derive", + "mlua-sys", + "thiserror", +] + +[[package]] +name = "elua-derive" +version = "0.1.0" +dependencies = [ + "bytes", + "proc-macro2", + "quote", + "reqwest", + "syn 2.0.51", + "tempdir", + "zip", +] + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -814,6 +914,27 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "file-id" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6584280525fb2059cba3db2c04abf947a1a29a45ddae89f3870f8281704fafc9" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.4.1", + "windows-sys 0.52.0", +] + [[package]] name = "flate2" version = "1.0.28" @@ -833,6 +954,12 @@ dependencies = [ "spin", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foreign-types" version = "0.3.2" @@ -848,12 +975,36 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fps_counter" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3aaba7ff514ee9d802b562927f80b1e94e93d8e74c31b134c9c3762dabf1a36b" +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "fuchsia-cprng" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" + [[package]] name = "futures-channel" version = "0.3.30" @@ -903,6 +1054,33 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -912,6 +1090,16 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.11" @@ -940,7 +1128,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0af1827b7dd2f36d740ae804c1b3ea0d64c12533fb61ff91883005143a0e8c5a" dependencies = [ "core-foundation", - "inotify", + "inotify 0.10.2", "io-kit-sys", "js-sys", "libc", @@ -1017,7 +1205,7 @@ dependencies = [ "inflections", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -1084,6 +1272,25 @@ dependencies = [ "bitflags 2.4.1", ] +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.2.1" @@ -1099,15 +1306,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.14.3" @@ -1145,6 +1343,96 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.6", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "image" version = "0.24.7" @@ -1196,6 +1484,17 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + [[package]] name = "inotify" version = "0.10.2" @@ -1216,6 +1515,15 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -1249,6 +1557,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + [[package]] name = "itertools" version = "0.11.0" @@ -1258,6 +1572,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.10" @@ -1308,6 +1631,26 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -1413,7 +1756,9 @@ version = "0.1.0" dependencies = [ "anyhow", "lyra-ecs-derive", - "rand", + "lyra-math", + "paste", + "rand 0.8.5", "thiserror", ] @@ -1423,7 +1768,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -1447,12 +1792,13 @@ dependencies = [ "glam", "image", "instant", - "itertools", + "itertools 0.11.0", "lyra-ecs", + "lyra-math", "lyra-reflect", "lyra-resource", "quote", - "syn 2.0.48", + "syn 2.0.51", "thiserror", "tracing", "tracing-appender", @@ -1463,11 +1809,19 @@ dependencies = [ "winit", ] +[[package]] +name = "lyra-math" +version = "0.1.0" +dependencies = [ + "glam", +] + [[package]] name = "lyra-reflect" version = "0.1.0" dependencies = [ "lyra-ecs", + "lyra-math", "lyra-reflect-derive", ] @@ -1477,7 +1831,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -1486,12 +1840,15 @@ version = "0.0.1" dependencies = [ "anyhow", "base64 0.21.5", - "edict", + "crossbeam", "glam", "gltf", "image", "infer", + "lyra-ecs", "mime", + "notify", + "notify-debouncer-full", "percent-encoding", "thiserror", "tracing", @@ -1503,16 +1860,27 @@ name = "lyra-scripting" version = "0.1.0" dependencies = [ "anyhow", + "elua", + "itertools 0.12.0", "lyra-ecs", "lyra-game", "lyra-reflect", "lyra-resource", - "mlua", + "lyra-scripting-derive", "thiserror", "tracing", "tracing-subscriber", ] +[[package]] +name = "lyra-scripting-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.51", +] + [[package]] name = "mach2" version = "0.4.2" @@ -1597,24 +1965,11 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "mlua" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c81f8ac20188feb5461a73eabb22a34dd09d6d58513535eb587e46bff6ba250" -dependencies = [ - "bstr", - "mlua-sys", - "num-traits", - "once_cell", - "rustc-hash", -] - [[package]] name = "mlua-sys" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc29228347d6bdc9e613dc95c69df2817f755434ee0f7f3b27b57755fe238b7f" +checksum = "2847b42764435201d8cbee1f517edb79c4cca4181877b90047587c89e1b7bce4" dependencies = [ "cc", "cfg-if", @@ -1641,6 +1996,24 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.7.0" @@ -1706,6 +2079,39 @@ dependencies = [ "libc", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.1", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify 0.9.6", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f5dab59c348b9b50cf7f261960a20e389feb2713636399cd9082cd4b536154" +dependencies = [ + "crossbeam-channel", + "file-id", + "log", + "notify", + "parking_lot", + "walkdir", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1746,6 +2152,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "num_enum" version = "0.5.11" @@ -1785,7 +2201,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -1848,6 +2264,50 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.51", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "orbclient" version = "0.3.47" @@ -1901,6 +2361,35 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "hmac", + "password-hash", + "sha2", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1991,17 +2480,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" -[[package]] -name = "proc-easy" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea59c637cd0e6b71ae18e589854e9de9b7cb17fefdbf2047e42bd38e24285b19" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.48", -] - [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -2014,9 +2492,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.75" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] @@ -2045,6 +2523,19 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" +dependencies = [ + "fuchsia-cprng", + "libc", + "rand_core 0.3.1", + "rdrand", + "winapi", +] + [[package]] name = "rand" version = "0.8.5" @@ -2053,7 +2544,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2063,9 +2554,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] +[[package]] +name = "rand_core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +dependencies = [ + "rand_core 0.4.2", +] + +[[package]] +name = "rand_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" + [[package]] name = "rand_core" version = "0.6.4" @@ -2107,6 +2613,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +dependencies = [ + "rand_core 0.3.1", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -2125,12 +2640,61 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "renderdoc-sys" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" +[[package]] +name = "reqwest" +version = "0.11.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +dependencies = [ + "base64 0.21.5", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2170,12 +2734,39 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.5", +] + [[package]] name = "ryu" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2201,6 +2792,29 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.194" @@ -2218,7 +2832,7 @@ checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -2232,6 +2846,40 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2309,6 +2957,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spin" version = "0.9.8" @@ -2340,6 +2998,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -2353,15 +3017,65 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempdir" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" +dependencies = [ + "rand 0.4.6", + "remove_dir_all", +] + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand 2.0.1", + "redox_syscall 0.4.1", + "rustix 0.38.28", + "windows-sys 0.52.0", +] + [[package]] name = "termcolor" version = "1.4.0" @@ -2379,6 +3093,7 @@ dependencies = [ "async-std", "fps_counter", "lyra-engine", + "lyra-scripting", "tracing", ] @@ -2399,7 +3114,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -2452,12 +3167,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-fn" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7b2c33e09916c65a15c92c1e583946052527e06102689ed11c6125f64fa8ba" - [[package]] name = "tiny-skia" version = "0.8.4" @@ -2483,6 +3192,61 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2 0.5.6", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml_datetime" version = "0.6.5" @@ -2500,6 +3264,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.40" @@ -2531,7 +3301,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", ] [[package]] @@ -2580,18 +3350,45 @@ dependencies = [ "tracing-log 0.2.0", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "ttf-parser" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.11" @@ -2604,6 +3401,17 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "urlencoding" version = "2.1.3" @@ -2617,7 +3425,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" dependencies = [ "getrandom", - "rand", + "rand 0.8.5", ] [[package]] @@ -2632,6 +3440,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62ce5bb364b23e66b528d03168df78b38c0f7b6fe17386928f29d5ab2e7cb2f7" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "vec_map" version = "0.8.2" @@ -2650,6 +3464,25 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2677,7 +3510,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", "wasm-bindgen-shared", ] @@ -2711,7 +3544,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3218,6 +4051,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "x11-dl" version = "2.21.0" @@ -3258,7 +4101,56 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.51", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "aes", + "byteorder", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "flate2", + "hmac", + "pbkdf2", + "sha1", + "time", + "zstd", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3819d32..36a4517 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,11 +10,12 @@ members = [ "lyra-ecs", "lyra-reflect", "lyra-scripting", - "lyra-game"] + "lyra-game", "lyra-math"] [features] scripting = ["dep:lyra-scripting"] +lua_scripting = ["scripting", "lyra-scripting/lua"] [dependencies] lyra-game = { path = "lyra-game" } -lyra-scripting = { path = "lyra-scripting", optional = true } \ No newline at end of file +lyra-scripting = { path = "lyra-scripting", optional = true } diff --git a/examples/testbed/Cargo.toml b/examples/testbed/Cargo.toml index efe8e25..877d0b4 100644 --- a/examples/testbed/Cargo.toml +++ b/examples/testbed/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -lyra-engine = { path = "../../", version = "0.0.1", features = ["scripting"] } +lyra-engine = { path = "../../", version = "0.0.1", features = ["lua_scripting"] } +lyra-scripting = { path = "../../lyra-scripting", features = ["lua", "teal"] } #lyra-ecs = { path = "../../lyra-ecs"} anyhow = "1.0.75" async-std = "1.12.0" diff --git a/examples/testbed/scripts/test.lua b/examples/testbed/scripts/test.lua new file mode 100644 index 0000000..65a8460 --- /dev/null +++ b/examples/testbed/scripts/test.lua @@ -0,0 +1,85 @@ +function on_init() + local cube = world:request_res("assets/cube-texture-embedded.gltf") + print("Loaded textured cube") + + local pos = Transform.from_translation(Vec3.new(0, 0, -8.0)) + + local e = world:spawn(pos, cube) + print("spawned entity " .. tostring(e)) + + local handler = ActionHandler.new { + layouts = { 0 }, + actions = { + MoveForwardBackward = "Axis", + MoveLeftRight = "Axis", + MoveUpDown = "Axis", + LookLeftRight = "Axis", + LookUpDown = "Axis", + LookRoll = "Axis", + ObjectsMoveUpDown = "Axis" + }, + mappings = { + { + layout = 0, + binds = { + MoveForwardBackward = { + "key:w=1.0", "key:s=-1.0" + }, + MoveLeftRight = { + "key:a=-1.0", "key:d=1.0" + }, + MoveUpDown = { + "key:c=1.0", "key:z=-1.0" + }, + LookLeftRight = { + "key:left=-1.0", "key:right=1.0", + "mouse:axis:x" + }, + LookUpDown = { + "key:up=-1.0", "key:down=1.0", + "mouse:axis:y", + }, + LookRoll = { + "key:e=-1.0", "key:q=1.0", + }, + ObjectsMoveUpDown = { + "key:u=1.0", "key:j=-1.0" + } + } + } + } + } + + world:add_resource(handler) +end + +--[[ function on_first() + print("Lua's first function was called") +end + +function on_pre_update() + print("Lua's pre-update function was called") +end ]] + +function on_update() + ---@type number + local dt = world:resource(DeltaTime) + local act = world:resource(ActionHandler) + ---@type number + local move_objs = act:get_axis("ObjectsMoveUpDown") + + world:view(function (t) + if move_objs ~= nil then + t:translate(0, move_objs * 0.35 * dt, 0) + return t + end + end, Transform) +end + +--[[ function on_post_update() + print("Lua's post-update function was called") +end + +function on_last() + print("Lua's last function was called") +end ]] \ No newline at end of file diff --git a/examples/testbed/src/free_fly_camera.rs b/examples/testbed/src/free_fly_camera.rs index 63f8cc3..f4d7411 100644 --- a/examples/testbed/src/free_fly_camera.rs +++ b/examples/testbed/src/free_fly_camera.rs @@ -55,9 +55,9 @@ pub fn free_fly_camera_controller(delta_time: Res, handler: Res, handler: Res anyhow::Result<()> { { - let mut window_options = world.get_resource_mut::>(); + /* let mut window_options = world.get_resource_mut::>(); window_options.cursor_grab = CursorGrabMode::Confined; - window_options.cursor_visible = false; + window_options.cursor_visible = false; */ } let mut resman = world.get_resource_mut::(); //let diffuse_texture = resman.request::("assets/happy-tree.png").unwrap(); - let antique_camera_model = resman.request::("assets/AntiqueCamera.glb").unwrap(); + //let antique_camera_model = resman.request::("assets/AntiqueCamera.glb").unwrap(); //let cube_model = resman.request::("assets/cube-texture-bin.glb").unwrap(); let cube_model = resman.request::("assets/texture-sep/texture-sep.gltf").unwrap(); let crate_model = resman.request::("assets/crate/crate.gltf").unwrap(); drop(resman); - world.spawn(( + /* world.spawn(( ModelComponent(antique_camera_model), - TransformComponent::from(Transform::from_xyz(0.0, -5.0, -10.0)), - )); + Transform::from_xyz(0.0, -5.0, -10.0), + )); */ { let cube_tran = Transform::from_xyz(-3.5, 0.0, -8.0); //cube_tran.rotate_y(math::Angle::Degrees(180.0)); world.spawn(( - TransformComponent::from(cube_tran), + cube_tran, ModelComponent(crate_model.clone()), CubeFlag, )); @@ -117,7 +117,7 @@ async fn main() { diffuse: 1.0, specular: 1.3, }, - TransformComponent::from(light_tran), + light_tran, ModelComponent(cube_model.clone()), )); } @@ -139,7 +139,7 @@ async fn main() { diffuse: 7.0, specular: 1.0, }, - TransformComponent::from(light_tran), + Transform::from(light_tran), ModelComponent(cube_model.clone()), )); } @@ -183,7 +183,7 @@ async fn main() { diffuse: 1.0, specular: 1.3, }, - TransformComponent::from(light_tran), + Transform::from(light_tran), ModelComponent(cube_model), )); } @@ -206,7 +206,7 @@ async fn main() { Ok(()) }; let fps_plugin = move |game: &mut Game| { - let world = game.world(); + let world = game.world_mut(); world.add_resource(fps_counter::FPSCounter::new()); }; @@ -214,13 +214,13 @@ async fn main() { const SPEED: f32 = 4.0; let delta_time = **world.get_resource::(); - for (mut transform, _) in world.view_iter::<(&mut TransformComponent, &CubeFlag)>() { - let t = &mut transform.transform; + for (mut transform, _) in world.view_iter::<(&mut Transform, &CubeFlag)>() { + let t = &mut transform; t.rotate_y(math::Angle::Degrees(SPEED * delta_time)); } - for (mut transform, _s) in world.view_iter::<(&mut TransformComponent, &mut SpotLight)>() { - let t = &mut transform.transform; + for (mut transform, _s) in world.view_iter::<(&mut Transform, &mut SpotLight)>() { + let t = &mut transform; t.rotate_x(math::Angle::Degrees(SPEED * delta_time)); } @@ -228,19 +228,19 @@ async fn main() { }; let jiggle_plugin = move |game: &mut Game| { - game.world().add_resource(TpsAccumulator(0.0)); + game.world_mut().add_resource(TpsAccumulator(0.0)); let mut sys = BatchedSystem::new(); sys.with_criteria(FixedTimestep::new(45)); sys.with_system(spin_system.into_system()); //sys.with_system(fps_system); - game.with_system("fixed", sys, &[]); - fps_plugin(game); + //game.with_system("fixed", sys, &[]); + //fps_plugin(game); }; let action_handler_plugin = |game: &mut Game| { - let action_handler = ActionHandler::new() + /* let action_handler = ActionHandler::builder() .add_layout(LayoutId::from(0)) .add_action(CommonActionLabel::MoveForwardBackward, Action::new(ActionKind::Axis)) @@ -250,7 +250,7 @@ async fn main() { .add_action(CommonActionLabel::LookUpDown, Action::new(ActionKind::Axis)) .add_action(CommonActionLabel::LookRoll, Action::new(ActionKind::Axis)) - .add_mapping(ActionMapping::new(LayoutId::from(0), ActionMappingId::from(0)) + .add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0)) .bind(CommonActionLabel::MoveForwardBackward, &[ ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0) @@ -282,26 +282,31 @@ async fn main() { .finish() ); - /* #[allow(unused_variables)] - let test_system = |world: &mut World| -> anyhow::Result<()> { - let handler = world.get_resource::(); - - if let Some(alpha) = handler.get_axis_modifier("look_rotate") { - debug!("'look_rotate': {alpha}"); - } - - Ok(()) - }; */ - - game.world().add_resource(action_handler); + let world = game.world_mut(); + world.add_resource(action_handler); */ game.with_plugin(InputActionPlugin); - //game.with_system("input_test", test_system, &[]); + }; + + let script_test_plugin = |game: &mut Game| { + game.with_plugin(LuaScriptingPlugin); + + let world = game.world_mut(); + let mut res_man = world.get_resource_mut::(); + let script = res_man.request::("scripts/test.lua").unwrap(); + res_man.watch("scripts/test.lua", false).unwrap(); + drop(res_man); + + let script = Script::new("test.lua", script); + let scripts = ScriptList::new(vec![script]); + world.spawn((scripts,)); + }; Game::initialize().await .with_plugin(lyra_engine::DefaultPlugins) .with_startup_system(setup_sys.into_system()) .with_plugin(action_handler_plugin) + .with_plugin(script_test_plugin) //.with_plugin(fps_plugin) .with_plugin(jiggle_plugin) .with_plugin(FreeFlyCameraPlugin) diff --git a/lyra-ecs/Cargo.toml b/lyra-ecs/Cargo.toml index 5c9afd2..47643c8 100644 --- a/lyra-ecs/Cargo.toml +++ b/lyra-ecs/Cargo.toml @@ -5,10 +5,15 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +math = ["dep:lyra-math"] + [dependencies] lyra-ecs-derive = { path = "./lyra-ecs-derive" } +lyra-math = { path = "../lyra-math", optional = true } anyhow = "1.0.75" thiserror = "1.0.50" +paste = "1.0.14" [dev-dependencies] rand = "0.8.5" # used for tests diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index 52e1760..d00f607 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -1,6 +1,6 @@ use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::{DerefMut, Deref}}; -use crate::{world::{Entity, ArchetypeEntityId}, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick}; +use crate::{world::ArchetypeEntityId, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick, Entity}; #[derive(Clone)] pub struct ComponentColumn { @@ -210,6 +210,8 @@ impl ArchetypeId { pub struct Archetype { pub id: ArchetypeId, pub(crate) entities: HashMap, + /// map an Archetype entity id to an entity + pub(crate) ids_to_entity: HashMap, pub(crate) columns: Vec, pub capacity: usize, } @@ -226,6 +228,7 @@ impl Archetype { Archetype { id: new_id, entities: HashMap::new(), + ids_to_entity: HashMap::new(), columns, capacity: DEFAULT_CAPACITY, } @@ -246,16 +249,17 @@ impl Archetype { self.capacity = new_cap; } - let entity_index = self.entities.len(); - self.entities.insert(entity, ArchetypeEntityId(entity_index as u64)); + let entity_index = ArchetypeEntityId(self.entities.len() as u64); + self.entities.insert(entity, entity_index); + self.ids_to_entity.insert(entity_index, entity); bundle.take(|data, type_id, _size| { let col = self.get_column_mut(type_id).unwrap(); - unsafe { col.set_at(entity_index, data, *tick); } + unsafe { col.set_at(entity_index.0 as usize, data, *tick); } col.len += 1; }); - ArchetypeEntityId(entity_index as u64) + entity_index } /// Removes an entity from the Archetype and frees its components. Returns the entity record that took its place in the component column. @@ -286,6 +290,7 @@ impl Archetype { // safe from the .expect at the start of this method. self.entities.remove(&entity).unwrap(); + self.ids_to_entity.remove(&entity_index).unwrap(); // now change the ArchetypeEntityId to be the index that the moved entity was moved into. removed_entity.map(|(e, _a)| (e, entity_index)) @@ -351,14 +356,15 @@ impl Archetype { self.capacity = new_cap; } - let entity_index = self.entities.len(); - self.entities.insert(entity, ArchetypeEntityId(entity_index as u64)); + let entity_index = ArchetypeEntityId(self.entities.len() as u64); + self.entities.insert(entity, entity_index); + self.ids_to_entity.insert(entity_index, entity); for col in self.columns.iter_mut() { col.len += 1; } - ArchetypeEntityId(entity_index as u64) + entity_index } /// Moves the entity from this archetype into another one. @@ -401,6 +407,10 @@ impl Archetype { pub fn entities(&self) -> &HashMap { &self.entities } + + pub fn entity_of_index(&self, id: ArchetypeEntityId) -> Option { + self.ids_to_entity.get(&id).cloned() + } } #[cfg(test)] @@ -409,7 +419,7 @@ mod tests { use rand::Rng; - use crate::{tests::{Vec2, Vec3}, world::{Entity, EntityId}, bundle::Bundle, ComponentInfo, MemoryLayout, DynTypeId, DynamicBundle, Tick}; + use crate::{bundle::Bundle, tests::{Vec2, Vec3}, ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, MemoryLayout, Tick}; use super::Archetype; diff --git a/lyra-ecs/src/command.rs b/lyra-ecs/src/command.rs new file mode 100644 index 0000000..137f8f1 --- /dev/null +++ b/lyra-ecs/src/command.rs @@ -0,0 +1,150 @@ +use std::{any::Any, cell::RefMut, collections::VecDeque, ptr::{self, NonNull}}; + +use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World}; + +pub trait Command: Any { + fn as_any_boxed(self: Box) -> Box; + + fn run(self, world: &mut World) -> anyhow::Result<()>; +} + +impl Command for F +where + F: FnOnce(&mut World) -> anyhow::Result<()> + 'static +{ + fn as_any_boxed(self: Box) -> Box { + self + } + + fn run(self, world: &mut World) -> anyhow::Result<()> { + self(world) + } +} + +type RunCommand = unsafe fn(cmd: Box, world: &mut World) -> anyhow::Result<()>; + +#[derive(Default)] +pub struct CommandQueue(VecDeque<(RunCommand, Box)>); + +pub struct Commands<'a, 'b> { + queue: &'b mut CommandQueue, + entities: &'a mut Entities, +} + +impl<'a, 'b> Commands<'a, 'b> { + pub fn new(queue: &'b mut CommandQueue, world: &'a mut World) -> Self { + Self { + queue, + entities: &mut world.entities, + } + } + + /// Add a command to the end of the command queue + pub fn add(&mut self, cmd: C) { + let cmd = Box::new(cmd); + + let run_fn = |cmd: Box, world: &mut World| { + let cmd = cmd.as_any_boxed() + .downcast::() + .unwrap(); + + cmd.run(world)?; + + Ok(()) + }; + + self.queue.0.push_back((run_fn, cmd)); + } + + pub fn spawn(&mut self, bundle: B) -> Entity { + let e = self.entities.reserve(); + + self.add(move |world: &mut World| { + world.spawn_into(e, bundle); + Ok(()) + }); + + e + } + + /// Execute all commands in the queue, in order of insertion + pub fn execute(&mut self, world: &mut World) -> anyhow::Result<()> { + while let Some((cmd_fn, cmd_ptr)) = self.queue.0.pop_front() { + unsafe { + cmd_fn(cmd_ptr, world)?; + } + } + + Ok(()) + } +} + +impl FnArgFetcher for Commands<'_, '_> { + type State = CommandQueue; + type Arg<'a, 'state> = Commands<'a, 'state>; + + fn world_access(&self) -> Access { + Access::Write + } + + unsafe fn get<'a, 'state>(state: &'state mut Self::State, mut world_ptr: ptr::NonNull) -> Self::Arg<'a, 'state> { + let world = world_ptr.as_mut(); + Commands::new(state, world) + } + + fn create_state(_: NonNull) -> Self::State { + CommandQueue::default() + } + + fn apply_deferred<'a>(mut state: Self::State, mut world_ptr: NonNull) { + let world = unsafe { world_ptr.as_mut() }; + + let mut cmds = Commands::new(&mut state, world); + // safety: Commands has a mut borrow only to entities in the world + let world = unsafe { world_ptr.as_mut() }; + cmds.execute(world).unwrap() + } +} + +pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut) -> anyhow::Result<()> { + commands.execute(world)?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::ptr::NonNull; + + use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, Commands, DynTypeId, World}; + + #[test] + fn deferred_commands() { + let mut world = World::new(); + let vecs = vec![Vec2::rand(), Vec2::rand(), Vec2::rand()]; + world.spawn((vecs[0],)); + world.spawn((vecs[1],)); + world.spawn((vecs[2],)); + + let spawned_vec = Vec2::rand(); + + let spawned_vec_cl = spawned_vec.clone(); + let test_sys = move |mut commands: Commands| -> anyhow::Result<()> { + commands.spawn((spawned_vec_cl.clone(),)); + + Ok(()) + }; + + let mut graph_exec = GraphExecutor::new(); + graph_exec.insert_system("test", test_sys.into_system(), &[]); + graph_exec.execute(NonNull::from(&world), true).unwrap(); + + assert_eq!(world.entities.len(), 4); + + // there's only one archetype + let arch = world.archetypes.values().next().unwrap(); + let col = arch.get_column(DynTypeId::of::()).unwrap(); + let vec2: &Vec2 = unsafe { col.get(3) }; + assert_eq!(vec2.clone(), spawned_vec); + } +} \ No newline at end of file diff --git a/lyra-ecs/src/entity.rs b/lyra-ecs/src/entity.rs new file mode 100644 index 0000000..b12e04f --- /dev/null +++ b/lyra-ecs/src/entity.rs @@ -0,0 +1,62 @@ +use std::collections::{HashMap, VecDeque}; + +use crate::Record; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct EntityId(pub u64); + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct Entity { + pub(crate) id: EntityId, + pub(crate) generation: u64, +} + +pub struct Entities { + pub(crate) arch_index: HashMap, + dead: VecDeque, + next_id: EntityId, +} + +impl Default for Entities { + fn default() -> Self { + Self { + arch_index: Default::default(), + dead: Default::default(), + next_id: EntityId(0), + } + } +} + +impl Entities { + pub fn reserve(&mut self) -> Entity { + match self.dead.pop_front() { + Some(mut e) => { + e.generation += 1; + e + } + None => { + let new_id = self.next_id; + self.next_id.0 += 1; + + Entity { + id: new_id, + generation: 0, + } + } + } + } + + /// Returns the number of spawned entities + pub fn len(&self) -> usize { + self.next_id.0 as usize - self.dead.len() + } + + /// Retrieves the Archetype Record for the entity + pub(crate) fn entity_record(&self, entity: Entity) -> Option { + self.arch_index.get(&entity.id).cloned() + } + + pub(crate) fn insert_entity_record(&mut self, entity: Entity, record: Record) { + self.arch_index.insert(entity.id, record); + } +} diff --git a/lyra-ecs/src/lib.rs b/lyra-ecs/src/lib.rs index f42b377..a66af95 100644 --- a/lyra-ecs/src/lib.rs +++ b/lyra-ecs/src/lib.rs @@ -8,11 +8,19 @@ pub(crate) mod lyra_engine { } pub mod archetype; +use std::ops::BitOr; + pub use archetype::*; +pub mod entity; +pub use entity::*; + pub mod world; pub use world::*; +pub mod command; +pub use command::*; + pub mod bundle; pub use bundle::*; @@ -34,6 +42,10 @@ pub mod system; pub mod tick; pub use tick::*; +/// Implements Component for glam math types +#[cfg(feature = "math")] +pub mod math; + pub use lyra_ecs_derive::*; #[cfg(test)] @@ -44,4 +56,18 @@ pub enum Access { None, Read, Write, +} + +impl BitOr for Access { + type Output = Access; + + fn bitor(self, rhs: Self) -> Self::Output { + if self == Access::Write || rhs == Access::Write { + Access::Write + } else if self == Access::Read || rhs == Access::Read { + Access::Read + } else { + Access::None + } + } } \ No newline at end of file diff --git a/lyra-ecs/src/math.rs b/lyra-ecs/src/math.rs new file mode 100644 index 0000000..43c1a90 --- /dev/null +++ b/lyra-ecs/src/math.rs @@ -0,0 +1,20 @@ +use crate::Component; + +use lyra_math::{Vec3, Quat, Vec2, Vec4, Mat4, Transform}; + +macro_rules! impl_external_component { + ($type: ident) => { + impl Component for $type { + fn name() -> &'static str { + stringify!($type) + } + } + }; +} + +impl_external_component!(Vec2); +impl_external_component!(Vec3); +impl_external_component!(Vec4); +impl_external_component!(Mat4); +impl_external_component!(Quat); +impl_external_component!(Transform); \ No newline at end of file diff --git a/lyra-ecs/src/query/borrow.rs b/lyra-ecs/src/query/borrow.rs index 4ee6739..ce13ac2 100644 --- a/lyra-ecs/src/query/borrow.rs +++ b/lyra-ecs/src/query/borrow.rs @@ -221,7 +221,7 @@ impl AsQuery for &mut T { mod tests { use std::{mem::size_of, marker::PhantomData, ptr::NonNull}; - use crate::{world::{World, Entity, EntityId}, archetype::{Archetype, ArchetypeId}, query::{View, Fetch}, tests::Vec2, bundle::Bundle, DynTypeId, Tick}; + use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{Fetch, View}, tests::Vec2, world::World, DynTypeId, Entity, EntityId, Tick}; use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut}; diff --git a/lyra-ecs/src/query/entities.rs b/lyra-ecs/src/query/entities.rs index b1c28df..a75c33c 100644 --- a/lyra-ecs/src/query/entities.rs +++ b/lyra-ecs/src/query/entities.rs @@ -1,4 +1,4 @@ -use crate::{world::{Entity, World}, archetype::Archetype}; +use crate::{archetype::Archetype, world::World, Entity}; use super::{Fetch, Query, AsQuery}; diff --git a/lyra-ecs/src/query/view.rs b/lyra-ecs/src/query/view.rs index 43442d6..504c658 100644 --- a/lyra-ecs/src/query/view.rs +++ b/lyra-ecs/src/query/view.rs @@ -131,7 +131,7 @@ impl<'a, Q: Query> ViewOne<'a, Q> { } pub fn get(&self) -> Option> { - if let Some(record) = self.world.entity_index.get(&self.entity) { + if let Some(record) = self.world.entities.arch_index.get(&self.entity) { let arch = self.world.archetypes.get(&record.id) .expect("An invalid record was specified for an entity"); diff --git a/lyra-ecs/src/resource.rs b/lyra-ecs/src/resource.rs index 019067b..ae48fc6 100644 --- a/lyra-ecs/src/resource.rs +++ b/lyra-ecs/src/resource.rs @@ -6,7 +6,7 @@ impl ResourceObject for T {} /// A type erased storage for a Resource. pub struct ResourceData { - data: Box>, + pub(crate) data: Box>, type_id: TypeId, } diff --git a/lyra-ecs/src/system/batched.rs b/lyra-ecs/src/system/batched.rs index a9ed48e..d924d0f 100644 --- a/lyra-ecs/src/system/batched.rs +++ b/lyra-ecs/src/system/batched.rs @@ -79,6 +79,10 @@ impl System for BatchedSystem { Ok(()) } + + fn execute_deferred(&mut self, _: std::ptr::NonNull) -> anyhow::Result<()> { + todo!() + } } impl IntoSystem<()> for BatchedSystem { diff --git a/lyra-ecs/src/system/fn_sys.rs b/lyra-ecs/src/system/fn_sys.rs index 08a6b9d..fd7ac5c 100644 --- a/lyra-ecs/src/system/fn_sys.rs +++ b/lyra-ecs/src/system/fn_sys.rs @@ -1,13 +1,17 @@ -use std::{ptr::NonNull, marker::PhantomData}; +use std::{any::Any, marker::PhantomData, ptr::NonNull}; +use paste::paste; use crate::{world::World, Access, ResourceObject, query::{Query, View, AsQuery, ResMut, Res}}; use super::{System, IntoSystem}; pub trait FnArgFetcher { - type Arg<'a>: FnArg; + /// stores data that persists after an execution of a system + type State: 'static; + + type Arg<'a, 'state>: FnArgFetcher; - fn new() -> Self; + fn create_state(world: NonNull) -> Self::State; /// Return the appropriate world access if this fetcher gets the world directly. /// Return [`Access::None`] if you're only fetching components, or resources. @@ -20,7 +24,10 @@ pub trait FnArgFetcher { /// # Safety /// The system executor must ensure that on execution of the system, it will be safe to /// borrow the world. - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a>; + unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state>; + + /// Apply some action after the system was ran. + fn apply_deferred(state: Self::State, world: NonNull); } pub trait FnArg { @@ -29,8 +36,10 @@ pub trait FnArg { pub struct FnSystem { inner: F, - #[allow(dead_code)] - args: Args, + //#[allow(dead_code)] + //args: Args, + arg_state: Option>>, + _marker: PhantomData, } macro_rules! impl_fn_system_tuple { @@ -38,47 +47,65 @@ macro_rules! impl_fn_system_tuple { #[allow(non_snake_case)] impl System for FnSystem where - F: for<'a> FnMut($($name::Arg<'a>,)+) -> anyhow::Result<()>, + F: for<'a> FnMut($($name::Arg<'a, '_>,)+) -> anyhow::Result<()>, { fn world_access(&self) -> Access { todo!() } fn execute(&mut self, world: NonNull) -> anyhow::Result<()> { - $(let $name = unsafe { $name::new().get(world) };)+ + unsafe { + paste! { + $( + // get the arg fetcher, create its state, and get the arg + let mut []: $name::State = $name::create_state(world); + let [<$name:lower>] = $name::get(&mut [], world); + )+ + + (self.inner)($( [<$name:lower>] ),+)?; + + let mut state = Vec::new(); + $( + // type erase the now modified state, and store it + let boxed = Box::new([]) as Box; + state.push(boxed); + )+ + + self.arg_state = Some(state); + } + + Ok(()) + } + } + + fn execute_deferred(&mut self, world: NonNull) -> anyhow::Result<()> { + let state = self.arg_state.as_mut().expect("Somehow there was no state"); + state.reverse(); + + $( + let arg_state_box = state.pop() + .expect("Missing expected arg state"); + let arg_state = *arg_state_box.downcast::<$name::State>() + .expect("Somehow the state cannot be downcasted from boxed Any"); + $name::apply_deferred(arg_state, world); + )+ - (self.inner)($($name,)+)?; - Ok(()) } } - /* impl IntoSystem for F + impl IntoSystem<($($name,)+)> for F where F: FnMut($($name,)+) -> anyhow::Result<()>, - F: for<'a> FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a>,)+) -> anyhow::Result<()>, + F: for<'a> FnMut($($name::Arg<'a, '_>,)+) -> anyhow::Result<()>, { - type System = FnSystem; + type System = FnSystem; fn into_system(self) -> Self::System { FnSystem { - args: ($($name::Fetcher::new(),)+), - inner: self - } - } - } */ - - impl IntoSystem<($($name,)+)> for F - where - F: FnMut($($name,)+) -> anyhow::Result<()>, - F: for<'a> FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a>,)+) -> anyhow::Result<()>, - { - type System = FnSystem; - - fn into_system(self) -> Self::System { - FnSystem { - args: ($($name::Fetcher::new(),)+), - inner: self + inner: self, + arg_state: None, + _marker: PhantomData::<($($name,)+)>::default(), } } } @@ -103,127 +130,116 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O } impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P } /// An ArgFetcher implementation for query [`View`]s -pub struct ViewArgFetcher { +/* pub struct ViewArgFetcher { query: Q::Query } impl<'a, Q: AsQuery> FnArg for View<'a, Q> { type Fetcher = ViewArgFetcher; -} +} */ -impl FnArgFetcher for ViewArgFetcher { - type Arg<'a> = View<'a, Q>; - - fn new() -> Self { - ViewArgFetcher { - query: ::new(), - } - } +impl<'c, Q> FnArgFetcher for View<'c, Q> +where + Q: AsQuery, + ::Query: 'static +{ + type State = Q::Query; + type Arg<'a, 'state> = View<'a, Q>; fn world_access(&self) -> Access { todo!() } - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a> { + unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state> { let world = &*world.as_ptr(); let arch = world.archetypes.values().collect(); - let v = View::new(world, self.query, arch); + let v = View::new(world, state.clone(), arch); v } + + fn apply_deferred(_: Self::State, _: NonNull) { } + + fn create_state(_: NonNull) -> Self::State { + ::new() + } } /// An ArgFetcher implementation for borrowing the [`World`]. -pub struct WorldArgFetcher; +/* pub struct WorldArgFetcher; impl<'a> FnArg for &'a World { type Fetcher = WorldArgFetcher; -} +} */ -impl FnArgFetcher for WorldArgFetcher { - type Arg<'a> = &'a World; - - fn new() -> Self { - WorldArgFetcher - } +impl FnArgFetcher for &'_ World { + type State = (); + type Arg<'a, 'state> = &'a World; fn world_access(&self) -> Access { Access::Read } - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a> { + unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state> { &*world.as_ptr() } + fn apply_deferred(_: Self::State, _: NonNull) { } + + fn create_state(_: NonNull) -> Self::State { () } } -/// An ArgFetcher implementation for mutably borrowing the [`World`]. -pub struct WorldMutArgFetcher; - -impl<'a> FnArg for &'a mut World { - type Fetcher = WorldMutArgFetcher; -} - -impl FnArgFetcher for WorldMutArgFetcher { - type Arg<'a> = &'a mut World; - - fn new() -> Self { - WorldMutArgFetcher - } +impl FnArgFetcher for &'_ mut World { + type State = (); + type Arg<'a, 'state> = &'a mut World; fn world_access(&self) -> Access { Access::Write } - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a> { + unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state> { &mut *world.as_ptr() } + + fn apply_deferred(_: Self::State, _: NonNull) { } + + fn create_state(_: NonNull) -> Self::State { () } } -pub struct ResourceArgFetcher { +/* pub struct ResourceArgFetcher { phantom: PhantomData R> } impl<'a, R: ResourceObject> FnArg for Res<'a, R> { type Fetcher = ResourceArgFetcher; -} +} */ -impl FnArgFetcher for ResourceArgFetcher { - type Arg<'a> = Res<'a, R>; +impl FnArgFetcher for Res<'_, R> { + type State = (); + type Arg<'a, 'state> = Res<'a, R>; - fn new() -> Self { - ResourceArgFetcher { - phantom: PhantomData - } - } - - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a> { + unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state> { let world = world.as_ref(); Res(world.get_resource::()) } + + fn apply_deferred(_: Self::State, _: NonNull) { } + + fn create_state(_: NonNull) -> Self::State { () } } -pub struct ResourceMutArgFetcher { - phantom: PhantomData R> -} +impl FnArgFetcher for ResMut<'_, R> { + type State = (); + type Arg<'a, 'state> = ResMut<'a, R>; -impl<'a, R: ResourceObject> FnArg for ResMut<'a, R> { - type Fetcher = ResourceMutArgFetcher; -} - -impl FnArgFetcher for ResourceMutArgFetcher { - type Arg<'a> = ResMut<'a, R>; - - fn new() -> Self { - ResourceMutArgFetcher { - phantom: PhantomData - } - } - - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a> { + unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state> { let world = world.as_ref(); ResMut(world.get_resource_mut::()) } + + fn apply_deferred(_: Self::State, _: NonNull) { } + + fn create_state(_: NonNull) -> Self::State { () } } #[cfg(test)] diff --git a/lyra-ecs/src/system/graph.rs b/lyra-ecs/src/system/graph.rs index 4612fba..40ac0cc 100644 --- a/lyra-ecs/src/system/graph.rs +++ b/lyra-ecs/src/system/graph.rs @@ -2,14 +2,16 @@ use std::{collections::{HashMap, VecDeque, HashSet}, ptr::NonNull}; use super::System; -use crate::world::World; +use crate::{world::World, CommandQueue, Commands}; #[derive(thiserror::Error, Debug)] pub enum GraphExecutorError { #[error("could not find a system's dependency named `{0}`")] MissingSystem(String), #[error("system `{0}` returned with an error: `{1}`")] - SystemError(String, anyhow::Error) + SystemError(String, anyhow::Error), + #[error("a command returned with an error: `{0}`")] + Command(anyhow::Error) } /// A single system in the graph. @@ -56,7 +58,7 @@ impl GraphExecutor { } /// Executes the systems in the graph - pub fn execute(&mut self, world: NonNull, stop_on_error: bool) -> Result, GraphExecutorError> { + pub fn execute(&mut self, mut world_ptr: NonNull, stop_on_error: bool) -> Result, GraphExecutorError> { let mut stack = VecDeque::new(); let mut visited = HashSet::new(); @@ -69,7 +71,7 @@ impl GraphExecutor { while let Some(node) = stack.pop_front() { let system = self.systems.get_mut(node.as_str()).unwrap(); - if let Err(e) = system.system.execute(world) + if let Err(e) = system.system.execute(world_ptr) .map_err(|e| GraphExecutorError::SystemError(node, e)) { if stop_on_error { return Err(e); @@ -78,6 +80,36 @@ impl GraphExecutor { possible_errors.push(e); unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error } + + if let Err(e) = system.system.execute_deferred(world_ptr) + .map_err(|e| GraphExecutorError::Command(e)) { + + if stop_on_error { + return Err(e); + } + + possible_errors.push(e); + unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error + } + + let world = unsafe { world_ptr.as_mut() }; + if let Some(mut queue) = world.try_get_resource_mut::() { + // Safety: Commands only borrows world.entities when adding commands + let world = unsafe { world_ptr.as_mut() }; + let mut commands = Commands::new(&mut queue, world); + + let world = unsafe { world_ptr.as_mut() }; + if let Err(e) = commands.execute(world) + .map_err(|e| GraphExecutorError::Command(e)) { + + if stop_on_error { + return Err(e); + } + + possible_errors.push(e); + unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error + } + } } Ok(possible_errors) diff --git a/lyra-ecs/src/system/mod.rs b/lyra-ecs/src/system/mod.rs index ab87811..5cc4395 100644 --- a/lyra-ecs/src/system/mod.rs +++ b/lyra-ecs/src/system/mod.rs @@ -27,6 +27,8 @@ pub trait System { let _ = world; Ok(()) } + + fn execute_deferred(&mut self, world: NonNull) -> anyhow::Result<()>; } pub trait IntoSystem { diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 6d2cb98..1e06f71 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -1,21 +1,12 @@ -use std::{collections::{HashMap, VecDeque}, any::TypeId, cell::{Ref, RefMut}, ptr::NonNull}; +use std::{any::TypeId, cell::{Ref, RefMut}, collections::HashMap, ptr::NonNull}; -use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, query::{Query, ViewIter, View, AsQuery}, resource::ResourceData, query::{dynamic::DynamicView, ViewOne}, ComponentInfo, DynTypeId, TickTracker, Tick}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct EntityId(pub u64); +use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, View, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker}; /// The id of the entity for the Archetype. /// The Archetype struct uses this as the index in the component columns #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct ArchetypeEntityId(pub u64); -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct Entity { - pub(crate) id: EntityId, - pub(crate) generation: u64, -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Record { pub id: ArchetypeId, @@ -25,11 +16,9 @@ pub struct Record { pub struct World { pub(crate) archetypes: HashMap, next_archetype_id: ArchetypeId, - pub(crate) entity_index: HashMap, - dead_entities: VecDeque, - next_entity_id: EntityId, resources: HashMap, tracker: TickTracker, + pub(crate) entities: Entities, } impl Default for World { @@ -37,11 +26,9 @@ impl Default for World { Self { archetypes: HashMap::new(), next_archetype_id: ArchetypeId(0), - entity_index: HashMap::new(), - dead_entities: VecDeque::new(), - next_entity_id: EntityId(0), resources: HashMap::new(), tracker: TickTracker::new(), + entities: Entities::default(), } } } @@ -51,32 +38,30 @@ impl World { Self::default() } - /// Gets a new Entity, will recycle dead entities and increment their generation. - fn get_new_entity(&mut self) -> Entity { - match self.dead_entities.pop_front() { - Some(mut e) => { - e.generation += 1; - e - }, - None => { - let new_id = self.next_entity_id; - self.next_entity_id.0 += 1; - - Entity { - id: new_id, - generation: 0, - } - } - } + /// Reserves an entity in the world + pub fn reserve_entity(&mut self) -> Entity { + self.entities.reserve() } - /// Spawns a new entity and inserts the component `bundle` into it. pub fn spawn(&mut self, bundle: B) -> Entity + where + B: Bundle + { + let new_entity = self.reserve_entity(); + self.spawn_into(new_entity, bundle); + new_entity + } + + /// Spawn the components into a reserved entity. Only do this with entities that + /// were 'reserved' with [`World::reserve`] + /// + /// # Safety + /// Do not use this method with an entity that is currently alive, it WILL cause undefined behavior. + pub fn spawn_into(&mut self, entity: Entity, bundle: B) where B: Bundle { let bundle_types = bundle.type_ids(); - let new_entity = self.get_new_entity(); let tick = self.tick(); @@ -86,7 +71,11 @@ impl World { .find(|a| a.is_archetype_for(&bundle_types)); if let Some(archetype) = archetype { - let arche_idx = archetype.add_entity(new_entity, bundle, &tick); + // make at just one check to ensure you're not spawning twice + debug_assert!(!archetype.entities.contains_key(&entity), + "You attempted to spawn components into an entity that already exists!"); + + let arche_idx = archetype.add_entity(entity, bundle, &tick); // Create entity record and store it let record = Record { @@ -94,14 +83,14 @@ impl World { index: arche_idx, }; - self.entity_index.insert(new_entity.id, record); + self.entities.insert_entity_record(entity, record); } // create a new archetype if one isn't found else { // create archetype let new_arch_id = self.next_archetype_id.increment(); let mut archetype = Archetype::from_bundle_info(new_arch_id, bundle.info()); - let entity_arch_id = archetype.add_entity(new_entity, bundle, &tick); + let entity_arch_id = archetype.add_entity(entity, bundle, &tick); // store archetype self.archetypes.insert(new_arch_id, archetype); @@ -113,27 +102,25 @@ impl World { index: entity_arch_id, }; - self.entity_index.insert(new_entity.id, record); + self.entities.insert_entity_record(entity, record); } - - new_entity } /// Despawn an entity from the World pub fn despawn(&mut self, entity: Entity) { // Tick the tracker if the entity is spawned. This is done here instead of the `if let` // below due to the borrow checker complaining about multiple mutable borrows to self. - let tick = if self.entity_index.contains_key(&entity.id) { + let tick = if self.entities.arch_index.contains_key(&entity.id) { Some(self.tick()) } else { None }; - if let Some(record) = self.entity_index.get_mut(&entity.id) { + if let Some(record) = self.entities.arch_index.get_mut(&entity.id) { let tick = tick.unwrap(); let arch = self.archetypes.get_mut(&record.id).unwrap(); if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) { // replace the archetype index of the moved index with its new index. - self.entity_index.get_mut(&moved.id).unwrap().index = new_index; + self.entities.arch_index.get_mut(&moved.id).unwrap().index = new_index; } } } @@ -152,7 +139,7 @@ impl World { let tick = self.tick(); - let record = *self.entity_index.get(&entity.id).unwrap(); + let record = self.entities.entity_record(entity).unwrap(); let current_arch = self.archetypes.get(&record.id).unwrap(); let mut col_types: Vec = current_arch.columns.iter().map(|c| c.info.type_id).collect(); @@ -188,7 +175,7 @@ impl World { id: arch.id, index: res_index, }; - self.entity_index.insert(entity.id, new_record); + self.entities.insert_entity_record(entity, new_record); } else { let new_arch_id = self.next_archetype_id.increment(); let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos); @@ -202,13 +189,23 @@ impl World { index: entity_arch_id, }; - self.entity_index.insert(entity.id, record); + self.entities.insert_entity_record(entity, record); } let current_arch = self.archetypes.get_mut(&record.id).unwrap(); current_arch.remove_entity(entity, &tick); } + pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> { + self.entities.entity_record(entity) + .and_then(|record| self.archetypes.get(&record.id)) + } + + pub fn entity_archetype_mut(&mut self, entity: Entity) -> Option<&mut Archetype> { + self.entities.entity_record(entity) + .and_then(|record| self.archetypes.get_mut(&record.id)) + } + /// View into the world for a set of entities that satisfy the queries. pub fn view_iter(&self) -> ViewIter { let archetypes = self.archetypes.values().collect(); @@ -245,15 +242,29 @@ impl World { .get_mut() } + /// Get a resource from the world, or insert it into the world as its default. + pub fn get_resource_or_default(&mut self) -> RefMut + { + self.resources.entry(TypeId::of::()) + .or_insert_with(|| ResourceData::new(T::default())) + .get_mut() + } + /// Gets a resource from the World. /// /// Will panic if the resource is not in the world. See [`try_get_resource`] for /// a function that returns an option. pub fn get_resource(&self) -> Ref { - self.resources.get(&TypeId::of::()).unwrap() + self.resources.get(&TypeId::of::()) + .expect(&format!("World is missing resource of type '{}'", std::any::type_name::())) .get() } + /// Returns boolean indicating if the World contains a resource of type `T`. + pub fn has_resource(&self) -> bool { + self.resources.contains_key(&TypeId::of::()) + } + /// Attempts to get a resource from the World. /// /// Returns `None` if the resource was not found. @@ -267,7 +278,8 @@ impl World { /// Will panic if the resource is not in the world. See [`try_get_resource_mut`] for /// a function that returns an option. pub fn get_resource_mut(&self) -> RefMut { - self.resources.get(&TypeId::of::()).unwrap() + self.resources.get(&TypeId::of::()) + .expect(&format!("World is missing resource of type '{}'", std::any::type_name::())) .get_mut() } @@ -296,6 +308,12 @@ impl World { pub fn tick_tracker(&self) -> &TickTracker { &self.tracker } + + /// Attempts to find a resource in the world and returns a NonNull pointer to it + pub unsafe fn try_get_resource_ptr(&self) -> Option> { + self.resources.get(&TypeId::of::()) + .map(|d| unsafe { NonNull::new_unchecked(d.data.as_ptr() as *mut T) }) + } } #[cfg(test)] @@ -348,7 +366,7 @@ mod tests { world.despawn(middle_en); - let record = world.entity_index.get(&last_en.id).unwrap(); + let record = world.entities.entity_record(last_en).unwrap(); assert_eq!(record.index.0, 1); } diff --git a/lyra-game/Cargo.toml b/lyra-game/Cargo.toml index 87d853f..070fc85 100644 --- a/lyra-game/Cargo.toml +++ b/lyra-game/Cargo.toml @@ -5,8 +5,9 @@ edition = "2021" [dependencies] lyra-resource = { path = "../lyra-resource" } -lyra-ecs = { path = "../lyra-ecs" } -lyra-reflect = { path = "../lyra-reflect" } +lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } +lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] } +lyra-math = { path = "../lyra-math" } winit = "0.28.1" tracing = "0.1.37" diff --git a/lyra-game/src/delta_time.rs b/lyra-game/src/delta_time.rs index 9dbbff7..d074677 100644 --- a/lyra-game/src/delta_time.rs +++ b/lyra-game/src/delta_time.rs @@ -1,10 +1,11 @@ use instant::Instant; use lyra_ecs::{Component, world::World}; +use lyra_reflect::Reflect; use crate::{plugin::Plugin, game::GameStages}; -#[derive(Clone, Component)] -pub struct DeltaTime(f32, Option); +#[derive(Clone, Component, Default, Reflect)] +pub struct DeltaTime(f32, #[reflect(skip)] Option); impl std::ops::Deref for DeltaTime { type Target = f32; @@ -36,7 +37,7 @@ pub struct DeltaTimePlugin; impl Plugin for DeltaTimePlugin { fn setup(&self, game: &mut crate::game::Game) { - game.world().add_resource(DeltaTime(0.0, None)); + game.world_mut().add_resource(DeltaTime(0.0, None)); game.add_system_to_stage(GameStages::First, "delta_time", delta_time_system, &[]); } } \ No newline at end of file diff --git a/lyra-game/src/events.rs b/lyra-game/src/events.rs index aee0bb0..11f538a 100644 --- a/lyra-game/src/events.rs +++ b/lyra-game/src/events.rs @@ -78,6 +78,6 @@ pub struct EventsPlugin; impl Plugin for EventsPlugin { fn setup(&self, game: &mut crate::game::Game) { - game.world().add_resource(EventQueue::new()); + game.world_mut().add_resource(EventQueue::new()); } } \ No newline at end of file diff --git a/lyra-game/src/game.rs b/lyra-game/src/game.rs index 4ce2053..953b462 100755 --- a/lyra-game/src/game.rs +++ b/lyra-game/src/game.rs @@ -246,11 +246,17 @@ impl Game { } /// Get the world of this game - pub fn world(&mut self) -> &mut World { + pub fn world_mut(&mut self) -> &mut World { // world is always `Some`, so unwrapping is safe self.world.as_mut().unwrap() } + /// Get the world of this game + pub fn world(&self) -> &World { + // world is always `Some`, so unwrapping is safe + self.world.as_ref().unwrap() + } + /// Add a system to the ecs world pub fn with_system(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self where @@ -313,12 +319,14 @@ impl Game { self } - /// Add a plugin to the game. These will be executed before the window is initiated and opened + /// Add a plugin to the game. These are executed as they are added. pub fn with_plugin

(&mut self, plugin: P) -> &mut Self where P: Plugin + 'static { - self.plugins.push_back(Box::new(plugin)); + let plugin = Box::new(plugin); + plugin.as_ref().setup(self); + self.plugins.push_back(plugin); self } @@ -339,16 +347,12 @@ impl Game { tracing_subscriber::registry() .with(fmt::layer().with_writer(stdout_layer)) .with(filter::Targets::new() - .with_target("lyra_engine", Level::TRACE) - .with_target("wgpu_core", Level::WARN) - .with_default(Level::DEBUG)) + // done by prefix, so it includes all lyra subpackages + .with_target("lyra", Level::DEBUG) + .with_target("wgpu", Level::WARN) + .with_default(Level::INFO)) .init(); - // setup all the plugins - while let Some(plugin) = self.plugins.pop_front() { - plugin.as_ref().setup(self); - } - let world = self.world.take().unwrap_or_default(); // run startup systems diff --git a/lyra-game/src/input/action.rs b/lyra-game/src/input/action.rs index db60930..aa756bf 100644 --- a/lyra-game/src/input/action.rs +++ b/lyra-game/src/input/action.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, ops::Deref, hash::{Hash, DefaultHasher, Hasher}, use glam::Vec2; use lyra_ecs::world::World; +use lyra_reflect::Reflect; use crate::{plugin::Plugin, game::GameStages, EventQueue}; @@ -213,7 +214,7 @@ impl Action { } #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct LayoutId(u32); +pub struct LayoutId(pub u32); impl From for LayoutId { fn from(value: u32) -> Self { @@ -240,7 +241,7 @@ impl Layout { } #[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct ActionMappingId(u32); +pub struct ActionMappingId(pub u32); impl From for ActionMappingId { fn from(value: u32) -> Self { @@ -264,6 +265,10 @@ impl ActionMapping { } } + pub fn builder(layout: LayoutId, id: ActionMappingId) -> ActionMappingBuilder { + ActionMappingBuilder::new(ActionMapping::new(layout, id)) + } + /// Creates a binding for the action. /// /// If the action is not in this layout, this will panic! @@ -271,7 +276,7 @@ impl ActionMapping { /// Parameters: /// * `action` - The label corresponding to the action in this Layout. /// * `bind` - The Binding to add to the Action. - pub fn bind(mut self, action: L, bindings: &[Binding]) -> Self + pub fn bind(&mut self, action: L, bindings: &[Binding]) -> &mut Self where L: ActionLabel { @@ -283,32 +288,48 @@ impl ActionMapping { self } - /// Creates multiple binding for the action. - /// - /// If the action is not in this layout, this will panic! - /// - /// Parameters: - /// * `action_label` - The label corresponding to the action in this Layout. - /// * `bindings` - The list of Bindings to add to the Action. - /* pub fn add_bindings(&mut self, action_label: String, bindings: &[Binding]) -> &mut Self { - let mut bindings = bindings.to_vec(); - let action_binds = self.action_binds.entry(action_label) - .or_insert_with(Vec::new); - action_binds.append(&mut bindings); - - self - } */ - pub fn finish(self) -> Self { self } } -#[derive(Clone, Default)] +pub struct ActionMappingBuilder { + mapping: ActionMapping, +} + +impl ActionMappingBuilder { + fn new(mapping: ActionMapping) -> Self { + Self { + mapping, + } + } + + pub fn bind(mut self, action: L, bindings: &[Binding]) -> Self + where + L: ActionLabel + { + let mut bindings = bindings.to_vec(); + + let action_binds = self.mapping.action_binds.entry(action.label_hash()).or_default(); + action_binds.append(&mut bindings); + + self + } + + pub fn finish(self) -> ActionMapping { + self.mapping + } +} + +#[derive(Clone, Default, Reflect)] pub struct ActionHandler { + #[reflect(skip)] // TODO: dont just skip all these pub actions: HashMap, + #[reflect(skip)] pub layouts: HashMap, + #[reflect(skip)] pub current_layout: LayoutId, + #[reflect(skip)] pub current_mapping: ActionMappingId, } @@ -317,26 +338,31 @@ impl ActionHandler { Self::default() } - pub fn add_layout(mut self, id: LayoutId) -> Self { - self.layouts.insert(id, Layout::new()); - - self + pub fn builder() -> ActionHandlerBuilder { + ActionHandlerBuilder::default() } - pub fn add_action(mut self, label: L, action: Action) -> Self + pub fn add_layout(&mut self, id: LayoutId) { + self.layouts.insert(id, Layout::new()); + } + + pub fn action(&self, label: L) -> Option<&Action> + where + L: ActionLabel + { + self.actions.get(&label.label_hash()) + } + + pub fn add_action(&mut self, label: L, action: Action) where L: ActionLabel { self.actions.insert(label.label_hash(), action); - - self } - pub fn add_mapping(mut self, mapping: ActionMapping) -> Self { + pub fn add_mapping(&mut self, mapping: ActionMapping) { let layout = self.layouts.get_mut(&mapping.layout).unwrap(); layout.add_mapping(mapping); - - self } /// Returns true if the action is pressed (or was just pressed). @@ -443,6 +469,43 @@ impl ActionHandler { } } +#[derive(Default)] +pub struct ActionHandlerBuilder { + handler: ActionHandler, +} + +impl ActionHandlerBuilder { + pub fn new() -> Self { + Self::default() + } + + pub fn add_layout(mut self, id: LayoutId) -> Self { + self.handler.layouts.insert(id, Layout::new()); + + self + } + + pub fn add_action(mut self, label: L, action: Action) -> Self + where + L: ActionLabel + { + self.handler.actions.insert(label.label_hash(), action); + + self + } + + pub fn add_mapping(mut self, mapping: ActionMapping) -> Self { + let layout = self.handler.layouts.get_mut(&mapping.layout).unwrap(); + layout.add_mapping(mapping); + + self + } + + pub fn finish(self) -> ActionHandler { + self.handler + } +} + fn actions_system(world: &mut World) -> anyhow::Result<()> { let keys = world.try_get_resource::>() .map(|r| r.deref().clone()); diff --git a/lyra-game/src/input/mod.rs b/lyra-game/src/input/mod.rs index f03bd5f..bfa8175 100644 --- a/lyra-game/src/input/mod.rs +++ b/lyra-game/src/input/mod.rs @@ -11,4 +11,182 @@ pub mod buttons; pub use buttons::*; pub mod action; -pub use action::*; \ No newline at end of file +pub use action::*; + +pub type KeyCode = winit::event::VirtualKeyCode; + +/// Parses a [`KeyCode`] from a [`&str`]. +/// +/// There are some changes to a few keycodes. All the number keys `Key1`, `Key2`, etc., have +/// the `Key` prefix removed; so they are expected to be `1`, `2`, etc. +pub fn keycode_from_str(s: &str) -> Option { + let s = s.to_lowercase(); + let s = s.as_str(); + + match s { + "1" => Some(KeyCode::Key1), + "2" => Some(KeyCode::Key2), + "3" => Some(KeyCode::Key3), + "4" => Some(KeyCode::Key4), + "5" => Some(KeyCode::Key5), + "6" => Some(KeyCode::Key6), + "7" => Some(KeyCode::Key7), + "8" => Some(KeyCode::Key8), + "9" => Some(KeyCode::Key9), + "0" => Some(KeyCode::Key0), + "a" => Some(KeyCode::A), + "b" => Some(KeyCode::B), + "c" => Some(KeyCode::C), + "d" => Some(KeyCode::D), + "e" => Some(KeyCode::E), + "f" => Some(KeyCode::F), + "g" => Some(KeyCode::G), + "h" => Some(KeyCode::H), + "i" => Some(KeyCode::I), + "j" => Some(KeyCode::J), + "k" => Some(KeyCode::K), + "l" => Some(KeyCode::L), + "m" => Some(KeyCode::M), + "n" => Some(KeyCode::N), + "o" => Some(KeyCode::O), + "p" => Some(KeyCode::P), + "q" => Some(KeyCode::Q), + "r" => Some(KeyCode::R), + "s" => Some(KeyCode::S), + "t" => Some(KeyCode::T), + "u" => Some(KeyCode::U), + "v" => Some(KeyCode::V), + "w" => Some(KeyCode::W), + "x" => Some(KeyCode::X), + "y" => Some(KeyCode::Y), + "z" => Some(KeyCode::Z), + "escape" => Some(KeyCode::Escape), + "f1" => Some(KeyCode::F1), + "f2" => Some(KeyCode::F2), + "f3" => Some(KeyCode::F3), + "f4" => Some(KeyCode::F4), + "f5" => Some(KeyCode::F5), + "f6" => Some(KeyCode::F6), + "f7" => Some(KeyCode::F7), + "f8" => Some(KeyCode::F8), + "f9" => Some(KeyCode::F9), + "f10" => Some(KeyCode::F10), + "f11" => Some(KeyCode::F11), + "f12" => Some(KeyCode::F12), + "f13" => Some(KeyCode::F13), + "f14" => Some(KeyCode::F14), + "f15" => Some(KeyCode::F15), + "f16" => Some(KeyCode::F16), + "f17" => Some(KeyCode::F17), + "f18" => Some(KeyCode::F18), + "f19" => Some(KeyCode::F19), + "f20" => Some(KeyCode::F20), + "f21" => Some(KeyCode::F21), + "f22" => Some(KeyCode::F22), + "f23" => Some(KeyCode::F23), + "f24" => Some(KeyCode::F24), + "snapshot" => Some(KeyCode::Snapshot), + "scroll" => Some(KeyCode::Scroll), + "pause" => Some(KeyCode::Pause), + "insert" => Some(KeyCode::Insert), + "home" => Some(KeyCode::Home), + "delete" => Some(KeyCode::Delete), + "end" => Some(KeyCode::End), + "pagedown" => Some(KeyCode::PageDown), + "pageup" => Some(KeyCode::PageUp), + "left" => Some(KeyCode::Left), + "up" => Some(KeyCode::Up), + "right" => Some(KeyCode::Right), + "down" => Some(KeyCode::Down), + "back" => Some(KeyCode::Back), + "return" => Some(KeyCode::Return), + "space" => Some(KeyCode::Space), + "compose" => Some(KeyCode::Compose), + "caret" => Some(KeyCode::Caret), + "numlock" => Some(KeyCode::Numlock), + "numpad0" => Some(KeyCode::Numpad0), + "numpad1" => Some(KeyCode::Numpad1), + "numpad2" => Some(KeyCode::Numpad2), + "numpad3" => Some(KeyCode::Numpad3), + "numpad4" => Some(KeyCode::Numpad4), + "numpad5" => Some(KeyCode::Numpad5), + "numpad6" => Some(KeyCode::Numpad6), + "numpad7" => Some(KeyCode::Numpad7), + "numpad8" => Some(KeyCode::Numpad8), + "numpad9" => Some(KeyCode::Numpad9), + "numpadadd" => Some(KeyCode::NumpadAdd), + "numpaddivide" => Some(KeyCode::NumpadDivide), + "numpaddecimal" => Some(KeyCode::NumpadDecimal), + "numpadcomma" => Some(KeyCode::NumpadComma), + "numpadenter" => Some(KeyCode::NumpadEnter), + "numpadequals" => Some(KeyCode::NumpadEquals), + "numpadmultiply" => Some(KeyCode::NumpadMultiply), + "numpadsubtract" => Some(KeyCode::NumpadSubtract), + "abntc1" => Some(KeyCode::AbntC1), + "abntc2" => Some(KeyCode::AbntC2), + "apostrophe" => Some(KeyCode::Apostrophe), + "apps" => Some(KeyCode::Apps), + "asterisk" => Some(KeyCode::Asterisk), + "at" => Some(KeyCode::At), + "ax" => Some(KeyCode::Ax), + "backslash" => Some(KeyCode::Backslash), + "calculator" => Some(KeyCode::Calculator), + "capital" => Some(KeyCode::Capital), + "colon" => Some(KeyCode::Colon), + "comma" => Some(KeyCode::Comma), + "convert" => Some(KeyCode::Convert), + "equals" => Some(KeyCode::Equals), + "grave" => Some(KeyCode::Grave), + "kana" => Some(KeyCode::Kana), + "kanji" => Some(KeyCode::Kanji), + "lalt" => Some(KeyCode::LAlt), + "lbracket" => Some(KeyCode::LBracket), + "lcontrol" => Some(KeyCode::LControl), + "lshift" => Some(KeyCode::LShift), + "lwin" => Some(KeyCode::LWin), + "mail" => Some(KeyCode::Mail), + "mediaselect" => Some(KeyCode::MediaSelect), + "mediastop" => Some(KeyCode::MediaStop), + "minus" => Some(KeyCode::Minus), + "mute" => Some(KeyCode::Mute), + "mycomputer" => Some(KeyCode::MyComputer), + "navigateforward" => Some(KeyCode::NavigateForward), + "navigatebackward" => Some(KeyCode::NavigateBackward), + "nexttrack" => Some(KeyCode::NextTrack), + "noconvert" => Some(KeyCode::NoConvert), + "oem102" => Some(KeyCode::OEM102), + "period" => Some(KeyCode::Period), + "playpause" => Some(KeyCode::PlayPause), + "plus" => Some(KeyCode::Plus), + "power" => Some(KeyCode::Power), + "prevtrack" => Some(KeyCode::PrevTrack), + "ralt" => Some(KeyCode::RAlt), + "rbracket" => Some(KeyCode::RBracket), + "rcontrol" => Some(KeyCode::RControl), + "rshift" => Some(KeyCode::RShift), + "rwin" => Some(KeyCode::RWin), + "semicolon" => Some(KeyCode::Semicolon), + "slash" => Some(KeyCode::Slash), + "sleep" => Some(KeyCode::Sleep), + "stop" => Some(KeyCode::Stop), + "sysrq" => Some(KeyCode::Sysrq), + "tab" => Some(KeyCode::Tab), + "underline" => Some(KeyCode::Underline), + "unlabeled" => Some(KeyCode::Unlabeled), + "volumedown" => Some(KeyCode::VolumeDown), + "volumeup" => Some(KeyCode::VolumeUp), + "wake" => Some(KeyCode::Wake), + "webback" => Some(KeyCode::WebBack), + "webfavorites" => Some(KeyCode::WebFavorites), + "webforward" => Some(KeyCode::WebForward), + "webhome" => Some(KeyCode::WebHome), + "webrefresh" => Some(KeyCode::WebRefresh), + "websearch" => Some(KeyCode::WebSearch), + "webstop" => Some(KeyCode::WebStop), + "yen" => Some(KeyCode::Yen), + "copy" => Some(KeyCode::Copy), + "paste" => Some(KeyCode::Paste), + "cut" => Some(KeyCode::Cut), + _ => None + } +} \ No newline at end of file diff --git a/lyra-game/src/input/system.rs b/lyra-game/src/input/system.rs index 1a5171e..ec6b20f 100755 --- a/lyra-game/src/input/system.rs +++ b/lyra-game/src/input/system.rs @@ -8,8 +8,6 @@ use crate::{EventQueue, plugin::Plugin, game::GameStages}; use super::{events::*, InputButtons, InputEvent}; -pub type KeyCode = winit::event::VirtualKeyCode; - #[derive(Default)] pub struct InputSystem; @@ -120,6 +118,10 @@ impl crate::ecs::system::System for InputSystem { fn world_access(&self) -> lyra_ecs::Access { lyra_ecs::Access::Write } + + fn execute_deferred(&mut self, _: NonNull) -> anyhow::Result<()> { + Ok(()) + } } impl IntoSystem<()> for InputSystem { diff --git a/lyra-game/src/lib.rs b/lyra-game/src/lib.rs index 1445f40..85b2922 100644 --- a/lyra-game/src/lib.rs +++ b/lyra-game/src/lib.rs @@ -7,7 +7,6 @@ extern crate self as lyra_engine; pub mod game; pub mod render; pub mod resources; -pub mod math; pub mod input; pub mod castable_any; pub mod plugin; @@ -26,5 +25,10 @@ pub mod scene; pub use lyra_resource as assets; pub use lyra_ecs as ecs; +pub use lyra_math as math; +pub use lyra_reflect as reflect; + +#[cfg(feature = "scripting")] +pub use lyra_scripting as script; pub use plugin::DefaultPlugins; \ No newline at end of file diff --git a/lyra-game/src/plugin.rs b/lyra-game/src/plugin.rs index c7adca2..f1b8db7 100644 --- a/lyra-game/src/plugin.rs +++ b/lyra-game/src/plugin.rs @@ -1,3 +1,4 @@ +use lyra_ecs::CommandQueue; use lyra_resource::ResourceManager; use crate::EventsPlugin; @@ -98,7 +99,7 @@ pub struct ResourceManagerPlugin; impl Plugin for ResourceManagerPlugin { fn setup(&self, game: &mut Game) { - game.world().add_resource(ResourceManager::new()); + game.world_mut().add_resource(ResourceManager::new()); } } @@ -108,10 +109,22 @@ pub struct DefaultPlugins; impl Plugin for DefaultPlugins { fn setup(&self, game: &mut Game) { + CommandQueuePlugin.setup(game); EventsPlugin.setup(game); InputPlugin.setup(game); ResourceManagerPlugin.setup(game); WindowPlugin::default().setup(game); DeltaTimePlugin.setup(game); } +} + +/// A plugin that creates a CommandQueue, and inserts it into the world as a Resource. +/// The queue is processed at the end of every system execution in the GraphExecutor. +#[derive(Default)] +pub struct CommandQueuePlugin; + +impl Plugin for CommandQueuePlugin { + fn setup(&self, game: &mut Game) { + game.world_mut().add_resource(CommandQueue::default()); + } } \ No newline at end of file diff --git a/lyra-game/src/render/light/mod.rs b/lyra-game/src/render/light/mod.rs index d89cbd2..adbe0b5 100644 --- a/lyra-game/src/render/light/mod.rs +++ b/lyra-game/src/render/light/mod.rs @@ -8,11 +8,9 @@ pub use spotlight::*; use std::{collections::{VecDeque, HashMap}, marker::PhantomData}; -use tracing::debug; - use std::mem; -use crate::{math::Transform, scene::TransformComponent}; +use crate::math::Transform; use self::directional::DirectionalLight; @@ -169,29 +167,29 @@ impl LightUniformBuffers { pub fn update_lights(&mut self, queue: &wgpu::Queue, world_tick: Tick, world: &World) { for (entity, point_light, transform, light_epoch, transform_epoch) - in world.view_iter::<(Entities, &PointLight, &TransformComponent, TickOf, TickOf)>() { + in world.view_iter::<(Entities, &PointLight, &Transform, TickOf, TickOf)>() { if !self.point_lights.has_light(entity) || light_epoch == world_tick || transform_epoch == world_tick { - let uniform = PointLightUniform::from_bundle(&point_light, &transform.transform); + let uniform = PointLightUniform::from_bundle(&point_light, &transform); self.point_lights.update_or_add(&mut self.lights_uniform.point_lights, entity, uniform); - debug!("Updated point light"); + //debug!("Updated point light"); } } for (entity, spot_light, transform, light_epoch, transform_epoch) - in world.view_iter::<(Entities, &SpotLight, &TransformComponent, TickOf, TickOf)>() { + in world.view_iter::<(Entities, &SpotLight, &Transform, TickOf, TickOf)>() { if !self.spot_lights.has_light(entity) || light_epoch == world_tick || transform_epoch == world_tick { - let uniform = SpotLightUniform::from_bundle(&spot_light, &transform.transform); + let uniform = SpotLightUniform::from_bundle(&spot_light, &transform); self.spot_lights.update_or_add(&mut self.lights_uniform.spot_lights, entity, uniform); //debug!("Updated spot light"); } } if let Some((dir_light, transform)) = - world.view_iter::<(&DirectionalLight, &TransformComponent)>().next() { + world.view_iter::<(&DirectionalLight, &Transform)>().next() { - let uniform = DirectionalLightUniform::from_bundle(&dir_light, &transform.transform); + let uniform = DirectionalLightUniform::from_bundle(&dir_light, &transform); self.lights_uniform.directional_light = uniform; } diff --git a/lyra-game/src/render/material.rs b/lyra-game/src/render/material.rs index 162c561..dec5b12 100755 --- a/lyra-game/src/render/material.rs +++ b/lyra-game/src/render/material.rs @@ -11,11 +11,11 @@ pub struct MaterialSpecular { impl MaterialSpecular { pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc, value: &lyra_resource::Specular) -> Self { - let tex = value.texture.as_ref().map(|t| &t.data.as_ref().unwrap().image) - .map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), i, None).unwrap()); + let tex = value.texture.as_ref().map(|t| t.data_ref()) + .map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), &i.image, None).unwrap()); - let color_tex = value.color_texture.as_ref().map(|t| &t.data.as_ref().unwrap().image) - .map(|i| RenderTexture::from_image(device, queue, bg_layout, i, None).unwrap()); + let color_tex = value.color_texture.as_ref().map(|t| t.data_ref()) + .map(|i| RenderTexture::from_image(device, queue, bg_layout, &i.image, None).unwrap()); Self { factor: value.factor, @@ -39,8 +39,8 @@ pub struct Material { impl Material { pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc, value: &lyra_resource::Material) -> Self { - let diffuse_texture = value.base_color_texture.as_ref().map(|t| &t.data.as_ref().unwrap().image) - .map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), i, None).unwrap()); + let diffuse_texture = value.base_color_texture.as_ref().map(|t| t.data_ref()) + .map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), &i.image, None).unwrap()); let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s)); diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 9e250ee..a513d8f 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -16,7 +16,7 @@ use winit::window::Window; use crate::math::Transform; use crate::render::material::MaterialUniform; use crate::render::render_buffer::BufferWrapperBuilder; -use crate::scene::{ModelComponent, TransformComponent, CameraComponent}; +use crate::scene::{ModelComponent, CameraComponent}; use super::camera::{RenderCamera, CameraUniform}; use super::desc_buf_lay::DescVertexBufferLayout; @@ -395,13 +395,13 @@ impl Renderer for BasicRenderer { let now_inst = Instant::now(); - for (entity, model, model_epoch, transform, transform_epoch) in main_world.view_iter::<(Entities, &ModelComponent, TickOf, &TransformComponent, TickOf)>() { + for (entity, model, model_epoch, transform, transform_epoch) in main_world.view_iter::<(Entities, &ModelComponent, TickOf, &Transform, TickOf)>() { alive_entities.insert(entity); let cached = match self.entity_last_transforms.get_mut(&entity) { Some(last) if transform_epoch == last_epoch => { last.from_transform = last.to_transform; - last.to_transform = transform.transform; + last.to_transform = *transform; last.last_updated_at = Some(last.cached_at); last.cached_at = now_inst; @@ -412,8 +412,8 @@ impl Renderer for BasicRenderer { let cached = CachedTransform { last_updated_at: None, cached_at: now_inst, - from_transform: transform.transform, - to_transform: transform.transform, + from_transform: *transform, + to_transform: *transform, }; self.entity_last_transforms.insert(entity, cached.clone()); cached @@ -430,7 +430,7 @@ impl Renderer for BasicRenderer { let transform_val = cached.from_transform.lerp(cached.to_transform, alpha); - let model = model.data.as_ref().unwrap().as_ref(); + let model = model.data_ref(); for mesh in model.meshes.iter() { if !self.process_mesh(entity, transform_val, mesh) && model_epoch == last_epoch { self.update_mesh_buffers(entity, mesh); diff --git a/lyra-game/src/render/texture.rs b/lyra-game/src/render/texture.rs index c1b019f..aa99388 100755 --- a/lyra-game/src/render/texture.rs +++ b/lyra-game/src/render/texture.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use image::GenericImageView; -use lyra_resource::{Resource, Texture}; +use lyra_resource::{ResHandle, Texture}; use super::render_buffer::BindGroupPair; @@ -153,8 +153,8 @@ impl RenderTexture { }) } - pub fn update_texture(&mut self, _device: &wgpu::Device, queue: &wgpu::Queue, texture: &Arc>) { - let texture = &texture.data.as_ref().unwrap().image; + pub fn update_texture(&mut self, _device: &wgpu::Device, queue: &wgpu::Queue, texture: &Arc>) { + let texture = &texture.data_ref().image; let rgba = texture.to_rgba8(); let dimensions = texture.dimensions(); let size = wgpu::Extent3d { diff --git a/lyra-game/src/render/window.rs b/lyra-game/src/render/window.rs index a974544..d577996 100644 --- a/lyra-game/src/render/window.rs +++ b/lyra-game/src/render/window.rs @@ -373,7 +373,7 @@ impl Plugin for WindowPlugin { fn setup(&self, game: &mut crate::game::Game) { let window_options = WindowOptions::default(); - game.world().add_resource(Ct::new(window_options)); + game.world_mut().add_resource(Ct::new(window_options)); game.with_system("window_updater", window_updater_system, &[]); } } diff --git a/lyra-game/src/scene/mod.rs b/lyra-game/src/scene/mod.rs index 5d743fa..47898a1 100644 --- a/lyra-game/src/scene/mod.rs +++ b/lyra-game/src/scene/mod.rs @@ -4,9 +4,6 @@ pub use mesh::*; pub mod model; pub use model::*; -pub mod transform; -pub use transform::*; - pub mod camera; pub use camera::*; diff --git a/lyra-game/src/scene/model.rs b/lyra-game/src/scene/model.rs index 8e62541..2c5a6b3 100644 --- a/lyra-game/src/scene/model.rs +++ b/lyra-game/src/scene/model.rs @@ -1,10 +1,11 @@ use lyra_ecs::Component; +use lyra_reflect::Reflect; use lyra_resource::ResHandle; use crate::assets::Model; -#[derive(Clone, Component)] -pub struct ModelComponent(pub ResHandle); +#[derive(Clone, Component, Reflect)] +pub struct ModelComponent(#[reflect(skip)] pub ResHandle); impl From> for ModelComponent { fn from(value: ResHandle) -> Self { diff --git a/lyra-game/src/scene/transform.rs b/lyra-game/src/scene/transform.rs deleted file mode 100755 index 516f6f8..0000000 --- a/lyra-game/src/scene/transform.rs +++ /dev/null @@ -1,26 +0,0 @@ -use lyra_ecs::Component; - -use crate::math::Transform; - -#[derive(Clone, Component, Default)] -pub struct TransformComponent { - pub transform: Transform, -} - -impl From for TransformComponent { - fn from(transform: Transform) -> Self { - Self { - transform - } - } -} - -impl TransformComponent { - pub fn new() -> Self { - Self::default() - } - - pub fn from_transform(transform: Transform) -> Self { - Self::from(transform) - } -} \ No newline at end of file diff --git a/lyra-game/src/stage.rs b/lyra-game/src/stage.rs index e06e8aa..15d79ff 100644 --- a/lyra-game/src/stage.rs +++ b/lyra-game/src/stage.rs @@ -9,7 +9,9 @@ pub enum StagedExecutorError { #[error("[stage={0}] could not find a system's dependency named `{1}`")] MissingSystem(String, String), #[error("[stage={0}] system `{1}` returned with an error: `{2}`")] - SystemError(String, String, anyhow::Error) + SystemError(String, String, anyhow::Error), + #[error("[stage={0}] a command returned with an error: `{1}`")] + CommandError(String, anyhow::Error), } impl StagedExecutorError { @@ -17,6 +19,7 @@ impl StagedExecutorError { match value { GraphExecutorError::MissingSystem(s) => Self::MissingSystem(stage, s), GraphExecutorError::SystemError(s, e) => Self::SystemError(stage, s, e), + GraphExecutorError::Command(e) => Self::CommandError(stage, e) } } } diff --git a/lyra-math/Cargo.toml b/lyra-math/Cargo.toml new file mode 100644 index 0000000..463fe9b --- /dev/null +++ b/lyra-math/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "lyra-math" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +glam = { version = "0.24.0" } \ No newline at end of file diff --git a/lyra-game/src/math/angle.rs b/lyra-math/src/angle.rs similarity index 100% rename from lyra-game/src/math/angle.rs rename to lyra-math/src/angle.rs diff --git a/lyra-game/src/math/mod.rs b/lyra-math/src/lib.rs old mode 100755 new mode 100644 similarity index 100% rename from lyra-game/src/math/mod.rs rename to lyra-math/src/lib.rs diff --git a/lyra-game/src/math/transform.rs b/lyra-math/src/transform.rs similarity index 81% rename from lyra-game/src/math/transform.rs rename to lyra-math/src/transform.rs index 9e02ae1..cf9600c 100755 --- a/lyra-game/src/math/transform.rs +++ b/lyra-math/src/transform.rs @@ -1,9 +1,9 @@ use glam::{Vec3, Mat4, Quat}; -use crate::math::angle::Angle; +use super::Angle; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct Transform { pub translation: Vec3, pub rotation: Quat, @@ -85,12 +85,18 @@ impl Transform { pub fn rotate_z(&mut self, angle: Angle) { self.rotate(Quat::from_rotation_z(angle.to_radians())) } + + pub fn translate(&mut self, x: f32, y: f32, z: f32) { + let trans = &mut self.translation; + trans.x += x; + trans.y += y; + trans.z += z; + } /// Performs a linear interpolation between `self` and `rhs` based on the value `alpha`. /// /// When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`, the result - /// will be equal to `rhs`. When `alpha` is outside of range `[0, 1]`, the result is linearly - /// extrapolated. + /// will be equal to `rhs`. pub fn lerp(&self, rhs: Transform, alpha: f32) -> Self { if alpha.is_finite() { @@ -104,4 +110,18 @@ impl Transform { *self } } +} + +/// Adds a transform to another one +/// +/// The Translations of each transform is added and rotation and scale is multiplied. +impl std::ops::Add for Transform { + type Output = Transform; + + fn add(mut self, rhs: Self) -> Self::Output { + self.translation += rhs.translation; + self.rotation *= rhs.rotation; + self.scale *= rhs.scale; + self + } } \ No newline at end of file diff --git a/lyra-reflect/Cargo.toml b/lyra-reflect/Cargo.toml index db57f34..0977970 100644 --- a/lyra-reflect/Cargo.toml +++ b/lyra-reflect/Cargo.toml @@ -5,6 +5,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +math = ["dep:lyra-math"] + [dependencies] lyra-reflect-derive = { path = "lyra-reflect-derive" } -lyra-ecs = { path = "../lyra-ecs" } \ No newline at end of file +lyra-ecs = { path = "../lyra-ecs" } +lyra-math = { path = "../lyra-math", optional = true } \ No newline at end of file diff --git a/lyra-reflect/lyra-reflect-derive/src/enum_derive.rs b/lyra-reflect/lyra-reflect-derive/src/enum_derive.rs index 6a38896..a80740d 100644 --- a/lyra-reflect/lyra-reflect-derive/src/enum_derive.rs +++ b/lyra-reflect/lyra-reflect-derive/src/enum_derive.rs @@ -32,7 +32,7 @@ impl From<&Variant> for VariantType { /// Generates the following different outputs: /// -/// ```rust +/// ```compile_fail /// // for struct variants /// TestEnum::Error { msg, code } /// @@ -98,7 +98,7 @@ fn gen_variant_if(enum_id: &proc_macro2::Ident, variant: &Variant, if_body: proc /// Generates the following: /// -/// ```rust +/// ```compile_fail /// /// generated one field here /// if name == "msg" { /// return Some(msg); @@ -113,7 +113,7 @@ fn gen_variant_if(enum_id: &proc_macro2::Ident, variant: &Variant, if_body: proc fn gen_if_field_names(variant: &Variant) -> proc_macro2::TokenStream { let field_ifs = variant.fields.iter().map(|field| { let id = field.ident.as_ref().unwrap(); - let id_str = id.span().source_text().unwrap(); + let id_str = id.to_string(); quote! { if name == #id_str { @@ -129,7 +129,7 @@ fn gen_if_field_names(variant: &Variant) -> proc_macro2::TokenStream { /// Generates the following rust code: /// -/// ```rust +/// ```compile_fail /// match name { /// "msg" | "code" => true, /// _ => false, @@ -140,7 +140,7 @@ fn gen_match_names(variant: &Variant) -> proc_macro2::TokenStream { let field_name_strs = variant.fields.iter().map(|field| { let id = field.ident.as_ref() .expect("Could not find identifier for enum field!"); - id.span().source_text().unwrap() + id.to_string() }); quote! { @@ -153,7 +153,7 @@ fn gen_match_names(variant: &Variant) -> proc_macro2::TokenStream { /// Generates the following: /// -/// ```rust +/// ```compile_fail /// /// generated one field here /// if idx == 0 { /// return Some(a); @@ -190,7 +190,7 @@ fn gen_if_field_indices(variant: &Variant) -> proc_macro2::TokenStream { /// Generates the following: /// -/// ```rust +/// ```compile_fail /// /// generated one field here /// if idx == 0 { /// return Some("a"); @@ -226,7 +226,7 @@ fn gen_if_field_indices_names(variant: &Variant) -> proc_macro2::TokenStream { } /// Generates the following: -/// ```rust +/// ```compile_fail /// /// when `by_index` is false: /// /// if let TestEnum::Error{ msg, code} = self { @@ -292,7 +292,6 @@ fn gen_enum_if_stmts(enum_id: &proc_macro2::Ident, data: &DataEnum, by_index: bo _ => quote! { }, } }); - println!("===="); quote! { #( #struct_vars )* @@ -301,7 +300,7 @@ fn gen_enum_if_stmts(enum_id: &proc_macro2::Ident, data: &DataEnum, by_index: bo /// Generates the following rust code: /// -/// ```rust +/// ```compile_fail /// if let TestEnum::Error { msg, code } = self { /// return match name { /// // expands for continuing struct fields @@ -332,7 +331,7 @@ fn gen_enum_has_field(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc_mac /// Generates the following code: /// -/// ```rust +/// ```compile_fail /// match self { /// TestEnum::Start => 0, /// TestEnum::Middle(a, b) => 2, @@ -359,7 +358,7 @@ fn gen_enum_fields_len(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc_ma /// Generates the following code: /// -/// ```rust +/// ```compile_fail /// if let TestEnum::Error { msg, code } = self { /// if idx == 0 { /// return Some("msg"); @@ -390,7 +389,7 @@ fn gen_enum_field_name_at(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc } /// Generates the following code: -/// ```rust +/// ```compile_fail /// match self { /// TestEnum::Start => 0, /// TestEnum::Middle(a, b) => 1, @@ -428,7 +427,7 @@ fn gen_enum_variant_name(enum_id: &proc_macro2::Ident, data: &DataEnum, gen_inde /// Generates a match statement that returns the types of the variants of the enum. /// /// Example: -/// ```rust +/// ```compile_fail /// match self { /// TestEnum::Start => EnumType::Unit, /// TestEnum::Middle(a, b) => EnumType::Tuple, @@ -458,41 +457,35 @@ fn gen_enum_variant_type(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc_ /// Create a reflect implementation for an enum pub fn derive_reflect_enum(input: &DeriveInput, data_enum: &DataEnum) -> proc_macro2::TokenStream { - let type_path = &input.ident; - let name = type_path.span().source_text().unwrap(); - //println!("Got type path: {}", type_path); + let input_ident = &input.ident; + let ident_str = input.ident.to_string(); let variant_count = data_enum.variants.len(); - /* let mut variants_iter = data_enum.variants.iter(); + let field_ifs = gen_enum_if_stmts(input_ident, data_enum, false); + let field_mut_ifs = gen_enum_if_stmts(input_ident, data_enum, false); - let variant = variants_iter.next().unwrap(); - let variant_name = &variant.ident; */ + let field_at_ifs = gen_enum_if_stmts(input_ident, data_enum, true); + let field_at_mut_ifs = gen_enum_if_stmts(input_ident, data_enum, true); - let field_ifs = gen_enum_if_stmts(type_path, data_enum, false); - let field_mut_ifs = gen_enum_if_stmts(type_path, data_enum, false); - - let field_at_ifs = gen_enum_if_stmts(type_path, data_enum, true); - let field_at_mut_ifs = gen_enum_if_stmts(type_path, data_enum, true); - - let has_field = gen_enum_has_field(type_path, data_enum); - let field_len = gen_enum_fields_len(type_path, data_enum); - let field_name_at = gen_enum_field_name_at(type_path, data_enum); - let variant_name_match = gen_enum_variant_name(type_path, data_enum, false); - let variant_idx_match = gen_enum_variant_name(type_path, data_enum, true); - let variant_type = gen_enum_variant_type(type_path, data_enum); + let has_field = gen_enum_has_field(input_ident, data_enum); + let field_len = gen_enum_fields_len(input_ident, data_enum); + let field_name_at = gen_enum_field_name_at(input_ident, data_enum); + let variant_name_match = gen_enum_variant_name(input_ident, data_enum, false); + let variant_idx_match = gen_enum_variant_name(input_ident, data_enum, true); + let variant_type = gen_enum_variant_type(input_ident, data_enum); let generics = add_trait_bounds(input.generics.clone(), vec![parse_quote!(Reflect), parse_quote!(Clone)]); let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); return proc_macro2::TokenStream::from(quote! { - impl #impl_generics lyra_engine::reflect::Reflect for #type_path #ty_generics #where_clause { + impl #impl_generics lyra_engine::reflect::Reflect for #input_ident #ty_generics #where_clause { fn name(&self) -> ::std::string::String { - #name.to_string() + #ident_str.to_string() } fn type_id(&self) -> std::any::TypeId { - std::any::TypeId::of::<#type_path #ty_generics>() + std::any::TypeId::of::<#input_ident #ty_generics>() } fn as_any(&self) -> &dyn std::any::Any { @@ -530,7 +523,7 @@ pub fn derive_reflect_enum(input: &DeriveInput, data_enum: &DataEnum) -> proc_ma } } - impl #impl_generics lyra_engine::reflect::Enum for #type_path #ty_generics #where_clause { + impl #impl_generics lyra_engine::reflect::Enum for #input_ident #ty_generics #where_clause { fn field(&self, name: &str) -> Option<&dyn lyra_engine::reflect::Reflect> { let name = name.to_lowercase(); let name = name.as_str(); diff --git a/lyra-reflect/lyra-reflect-derive/src/lib.rs b/lyra-reflect/lyra-reflect-derive/src/lib.rs index b30e593..a8b8ff0 100644 --- a/lyra-reflect/lyra-reflect-derive/src/lib.rs +++ b/lyra-reflect/lyra-reflect-derive/src/lib.rs @@ -1,4 +1,5 @@ use proc_macro::TokenStream; +use proc_macro2::Ident; use quote::{quote, ToTokens}; use syn::{Generics, Path, Attribute, GenericParam, parse_macro_input, DeriveInput, TypeParamBound}; @@ -10,8 +11,55 @@ mod struct_derive; #[allow(unused_imports)] use struct_derive::*; +mod struct_macro; + +/* #[proc_macro_attribute(attributes(reflect))] +pub fn reflect(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { + item +} */ + +pub(crate) struct FieldAttributes(Vec); + +impl FieldAttributes { + /// Searches for a usage of the 'reflect' attribute and returns a list of the + /// things used in the usage. + pub fn from_vec(v: &Vec) -> Result { + let s: Result, _> = v.iter().filter_map(|att| match &att.meta { + syn::Meta::Path(_) => None, + syn::Meta::List(l) => { + Some(syn::parse::(l.tokens.clone().into())) + } + syn::Meta::NameValue(_) => None + }).collect(); + + Ok(Self(s?)) + } + + pub fn has_skip(&self) -> bool { + self.0.iter().any(|a| *a == ReflectAttribute::Skip) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum ReflectAttribute { + Skip +} + +impl syn::parse::Parse for ReflectAttribute { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ident: Ident = input.parse()?; + let ident_str = ident.to_string().to_lowercase(); + + match ident_str.as_str() { + "skip" => Ok(Self::Skip), + _ => Err(syn::Error::new(ident.span(), "Unknown reflect attribute flag")) + } + } +} + #[allow(dead_code)] pub(crate) struct ReflectDef { + //pub ident: Ident, pub type_path: Path, pub generics: Generics, pub attributes: Vec @@ -21,10 +69,12 @@ impl syn::parse::Parse for ReflectDef { fn parse(input: syn::parse::ParseStream) -> syn::Result { let attributes = input.call(Attribute::parse_outer)?; let type_path = Path::parse_mod_style(input)?; + //let ident = type_path. //type_path.require_ident()?; let mut generics = input.parse::()?; generics.where_clause = input.parse()?; Ok(Self { + //ident: ident.clone(), type_path, generics, attributes, @@ -32,7 +82,7 @@ impl syn::parse::Parse for ReflectDef { } } -#[proc_macro_derive(Reflect)] +#[proc_macro_derive(Reflect, attributes(reflect))] pub fn derive_reflect(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); @@ -55,18 +105,22 @@ pub fn derive_reflect(input: proc_macro::TokenStream) -> proc_macro::TokenStream #[proc_macro] pub fn impl_reflect_trait_value(input: TokenStream) -> TokenStream { let reflect = syn::parse_macro_input!(input as ReflectDef); - - let type_path = reflect.type_path.clone().into_token_stream(); - let name_id = &reflect.type_path.segments.last().unwrap().ident; - let name = name_id.span().source_text().unwrap(); + let type_path = reflect.type_path; + // convert the type path to a string. This would not create a leading separator + let type_path_str = { + let idents: Vec = type_path.segments.iter() + .map(|segment| segment.ident.to_string()) + .collect(); + idents.join("::") + }; let (impl_generics, ty_generics, where_clause) = reflect.generics.split_for_impl(); TokenStream::from(quote! { impl #impl_generics lyra_engine::reflect::Reflect for #type_path #ty_generics #where_clause { fn name(&self) -> ::std::string::String { - #name.to_string() + #type_path_str.to_string() } fn type_id(&self) -> std::any::TypeId { @@ -119,4 +173,9 @@ pub(crate) fn add_trait_bounds(mut generics: Generics, add_bounds: Vec TokenStream { + struct_macro::impl_reflect_simple_struct(input) } \ No newline at end of file diff --git a/lyra-reflect/lyra-reflect-derive/src/struct_derive.rs b/lyra-reflect/lyra-reflect-derive/src/struct_derive.rs index b5768ee..1f54547 100644 --- a/lyra-reflect/lyra-reflect-derive/src/struct_derive.rs +++ b/lyra-reflect/lyra-reflect-derive/src/struct_derive.rs @@ -1,13 +1,34 @@ use quote::{quote, ToTokens}; use syn::{DeriveInput, parse_quote, DataStruct}; -use crate::add_trait_bounds; +use crate::{add_trait_bounds, FieldAttributes}; + +#[derive(Debug, PartialEq, Eq)] +enum StructType { + Unit, + Named, + Tuple, +} + +impl StructType { + pub fn new(data: &DataStruct) -> Self { + if let Some(first) = data.fields.iter().next() { + if first.ident.is_some() { + Self::Named + } else { + Self::Tuple + } + } else { + Self::Unit + } + } +} /// Generates code that matches a string with a struct's field name, and returns an Option that /// contains a borrow (mutable borrow if `is_mut` is true) to the matching struct field. /// /// Example: -/// ```rust +/// ```compile_fail /// // when `is_mut` = false /// match name { /// "x" => Some(&self.x), @@ -22,35 +43,49 @@ use crate::add_trait_bounds; /// _ => None, /// } /// ``` +/// +/// If the struct is a unit or tuple struct, None will always be returned fn gen_struct_field_match(data: &DataStruct, is_mut: bool) -> proc_macro2::TokenStream { - let mut_tkn = if is_mut { - quote! { - mut - } - } else { quote!{} }; - - let field_arms = data.fields.iter().map(|field| { - let field_ident = field.ident.as_ref().expect("Struct is missing field ident!"); - let field_name_str = field_ident.to_string(); - - quote! { - #field_name_str => Some(&#mut_tkn self.#field_ident) - } - }); + let ty = StructType::new(data); - quote! { - match name { - #(#field_arms,)* - _ => None, + if ty == StructType::Named { + let mut_tkn = if is_mut { + quote! { + mut + } + } else { quote!{} }; + + let field_arms = data.fields.iter().map(|field| { + let field_ident = field.ident.as_ref().expect("Struct is missing field ident!"); + let field_name_str = field_ident.to_string(); + + let attrs = FieldAttributes::from_vec(&field.attrs) + .expect("Failure to parse reflect attributes"); + if attrs.has_skip() { + quote! { + #field_name_str => None + } + } else { + quote! { + #field_name_str => Some(&#mut_tkn self.#field_ident) + } + } + }); + + quote! { + match name { + #(#field_arms,)* + _ => None, + } } - } + } else { quote!(None) } } /// Generates code that matches a string with a struct's field name, and sets that field value /// with the provided `val`. /// /// Example: -/// ```rust +/// ```compile_fail /// match name { /// "x" => self.x = any_val.downcast_ref::() /// .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", "f32", val.name())) @@ -64,34 +99,48 @@ fn gen_struct_field_match(data: &DataStruct, is_mut: bool) -> proc_macro2::Token /// } /// ``` fn gen_struct_set_field_match(data: &DataStruct) -> proc_macro2::TokenStream { - let field_arms = data.fields.iter().map(|field| { - let field_ident = field.ident.as_ref().expect("Struct is missing field ident!"); - let field_name_str = field_ident.to_string(); - let field_ty = &field.ty; - - quote! { - #field_name_str => self.#field_ident = any_val.downcast_ref::<#field_ty>() - .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", #field_name_str, val.name())) - .clone() //Some(&#mut_tkn self.#field_ident) - } - }); + let ty = StructType::new(data); - quote! { - let any_val = val.as_any(); - match name { - #(#field_arms,)* - _ => { - return false; - }, + if ty == StructType::Named { + let field_arms = data.fields.iter().map(|field| { + let field_ident = field.ident.as_ref().expect("Struct is missing field ident!"); + let field_name_str = field_ident.to_string(); + let field_ty = &field.ty; + + let attrs = FieldAttributes::from_vec(&field.attrs) + .expect("Failure to parse reflect attributes"); + if attrs.has_skip() { + quote! { + #field_name_str => {} + } + } else { + quote! { + #field_name_str => self.#field_ident = any_val.downcast_ref::<#field_ty>() + .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", #field_name_str, val.name())) + .clone() //Some(&#mut_tkn self.#field_ident) + } + } + }); + + quote! { + let any_val = val.as_any(); + match name { + #(#field_arms,)* + _ => { + return false; + }, + } + + true } - } + } else { quote!(return false;) } } /// Generates code that matches a string with a struct's field name, and returns a string that is /// the type of the field. /// /// Example: -/// ```rust +/// ```compile_fail /// match name { /// "x" => Some("f32"), /// "y" => Some("f32"), @@ -99,32 +148,36 @@ fn gen_struct_set_field_match(data: &DataStruct) -> proc_macro2::TokenStream { /// } /// ``` fn gen_struct_field_name_match(data: &DataStruct) -> proc_macro2::TokenStream { - let field_arms = data.fields.iter().map(|field| { - let field_ident = field.ident.as_ref().expect("Struct is missing field ident!"); - let field_name_str = field_ident.to_string(); + let ty = StructType::new(data); + + if ty == StructType::Named { + let field_arms = data.fields.iter().map(|field| { + let field_ident = field.ident.as_ref().expect("Struct is missing field ident!"); + let field_name_str = field_ident.to_string(); + + let mut field_ty_stream = proc_macro2::TokenStream::new(); + field.ty.to_tokens(&mut field_ty_stream); + let s = field_ty_stream.to_string(); + + quote! { + #field_name_str => Some(#s) + } + }); - let mut field_ty_stream = proc_macro2::TokenStream::new(); - field.ty.to_tokens(&mut field_ty_stream); - let s = field_ty_stream.to_string(); - quote! { - #field_name_str => Some(#s) + match name { + #(#field_arms,)* + _ => None, + } } - }); - - quote! { - match name { - #(#field_arms,)* - _ => None, - } - } + } else { quote!(None) } } /// Generates code that matches a string with a struct's field name, and sets that field value /// with the provided `val`. /// /// Example: -/// ```rust +/// ```compile_fail /// match name { /// 0 => self.x = any_val.downcast_ref::() /// .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", "f32", val.name())) @@ -138,15 +191,38 @@ fn gen_struct_field_name_match(data: &DataStruct) -> proc_macro2::TokenStream { /// } /// ``` fn gen_struct_set_field_match_idx(data: &DataStruct) -> proc_macro2::TokenStream { + let ty = StructType::new(data); + + if ty == StructType::Unit { + return quote!( return false; ); + } + let field_arms = data.fields.iter().enumerate().map(|(idx, field)| { - let field_ident = field.ident.as_ref().expect("Struct is missing field ident!"); - let field_name_str = field_ident.to_string(); let field_ty = &field.ty; - - quote! { - #idx => self.#field_ident = any_val.downcast_ref::<#field_ty>() - .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", #field_name_str, val.name())) - .clone() + + let attrs = FieldAttributes::from_vec(&field.attrs) + .expect("Failure to parse reflect attributes"); + if attrs.has_skip() { + return quote! { + #idx => {} + }; + } + + if let Some(field_ident) = &field.ident { + let field_name_str = field_ident.to_string(); + + quote! { + #idx => self.#field_ident = any_val.downcast_ref::<#field_ty>() + .expect(&format!("Cannot set struct's field of {} to the provided type of {}", #field_name_str, val.name())) + .clone() + } + } else { + let sidx = syn::Index::from(idx); + quote! { + #idx => self.#sidx = any_val.downcast_ref::<#field_ty>() + .expect(&format!("Cannot set struct's field at {} to the provided type of {}", #idx, val.name())) + .clone() + } } }); @@ -158,6 +234,8 @@ fn gen_struct_set_field_match_idx(data: &DataStruct) -> proc_macro2::TokenStream return false; }, } + + true } } @@ -165,7 +243,7 @@ fn gen_struct_set_field_match_idx(data: &DataStruct) -> proc_macro2::TokenStream /// type of the field. /// /// Example: -/// ```rust +/// ```compile_fail /// match name { /// 0 => Some("f32"), /// 1 => Some("f32"), @@ -196,7 +274,7 @@ fn gen_struct_field_name_match_idx(data: &DataStruct) -> proc_macro2::TokenStrea /// to the matching struct field. /// /// Example: -/// ```rust +/// ```compile_fail /// // when `is_mut` = false /// match idx { /// 0 => Some(&self.x), @@ -212,6 +290,12 @@ fn gen_struct_field_name_match_idx(data: &DataStruct) -> proc_macro2::TokenStrea /// } /// ``` fn gen_struct_field_match_idx(data: &DataStruct, is_mut: bool) -> proc_macro2::TokenStream { + let ty = StructType::new(data); + + if ty == StructType::Unit { + return quote!(None); + } + let mut_tkn = if is_mut { quote! { mut @@ -219,10 +303,23 @@ fn gen_struct_field_match_idx(data: &DataStruct, is_mut: bool) -> proc_macro2::T } else { quote!{} }; let field_arms = data.fields.iter().enumerate().map(|(idx, field)| { - let field_ident = field.ident.as_ref().expect("Struct is missing field ident!"); + let attrs = FieldAttributes::from_vec(&field.attrs) + .expect("Failure to parse reflect attributes"); + if attrs.has_skip() { + return quote! { + #idx => None + }; + } - quote! { - #idx => Some(&#mut_tkn self.#field_ident) + if let Some(field_ident) = &field.ident { + quote! { + #idx => Some(&#mut_tkn self.#field_ident) + } + } else { + let sidx = syn::Index::from(idx); + quote! { + #idx => Some(&#mut_tkn self.#sidx) + } } }); @@ -238,7 +335,7 @@ fn gen_struct_field_match_idx(data: &DataStruct, is_mut: bool) -> proc_macro2::T /// and returns an Option that contains the name of the field. /// /// Example: -/// ```rust +/// ```compile_fail /// match idx { /// 0 => Some("x"), /// 1 => Some("y"), @@ -246,27 +343,34 @@ fn gen_struct_field_match_idx(data: &DataStruct, is_mut: bool) -> proc_macro2::T /// } /// ``` fn gen_struct_field_name_idx(data: &DataStruct) -> proc_macro2::TokenStream { - let field_arms = data.fields.iter().enumerate().map(|(idx, field)| { - let field_ident = field.ident.as_ref().expect("Struct is missing field ident!"); - let field_name_str = field_ident.to_string(); - - quote! { - #idx => Some(#field_name_str) - } - }); + let ty = StructType::new(data); - quote! { - match idx { - #(#field_arms,)* - _ => None, + if ty == StructType::Named { + let field_arms = data.fields.iter().enumerate().map(|(idx, field)| { + let field_ident = field.ident.as_ref().expect("Struct is missing field ident!"); + let field_name_str = field_ident.to_string(); + + quote! { + #idx => Some(#field_name_str) + } + }); + + quote! { + match idx { + #(#field_arms,)* + _ => None, + } } + } else { + quote!(None) } } /// Generates a token stream that implements Reflect and Struct for the provided struct pub fn derive_reflect_struct(input: &DeriveInput, data_struct: &DataStruct) -> proc_macro2::TokenStream { let type_path = &input.ident; - let name = type_path.span().source_text().unwrap(); + let name = type_path.to_string(); + //let name = type_path.span().source_text().unwrap(); let field_len = data_struct.fields.len(); let get_field_match = gen_struct_field_match(data_struct, false); @@ -360,12 +464,10 @@ pub fn derive_reflect_struct(input: &DeriveInput, data_struct: &DataStruct) -> p fn set_field(&mut self, name: &str, val: &dyn lyra_engine::reflect::Reflect) -> bool { #set_field_named - true } fn set_field_at(&mut self, idx: usize, val: &dyn lyra_engine::reflect::Reflect) -> bool { #set_field_idx - true } fn field_type(&self, name: &str) -> Option<&'static str> { diff --git a/lyra-reflect/lyra-reflect-derive/src/struct_macro.rs b/lyra-reflect/lyra-reflect-derive/src/struct_macro.rs new file mode 100644 index 0000000..d4971f8 --- /dev/null +++ b/lyra-reflect/lyra-reflect-derive/src/struct_macro.rs @@ -0,0 +1,270 @@ +use quote::quote; +use syn::{Token, parenthesized, punctuated::Punctuated}; + +struct Field { + name: syn::Ident, + _eq: Token![=], + ty: syn::Path, +} + +impl syn::parse::Parse for Field { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + Ok(Self { + name: input.parse()?, + _eq: input.parse()?, + ty: input.parse()?, + }) + } +} + +struct SimpleStruct { + type_path: syn::Path, + pub generics: syn::Generics, + fields: Vec, +} + +impl syn::parse::Parse for SimpleStruct { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + //let type_path = syn::Path::parse_mod_style(input)?; + let type_path: syn::Path = input.parse()?; + /* let mut generics = input.parse::()?; + generics.where_clause = input.parse()?; */ + + let mut fields = vec![]; + + // parse fields if a comma is found + if input.peek(Token![,]) { + let _: Token![,] = input.parse()?; + let ident: syn::Ident = input.parse()?; + let ident_str = ident.to_string(); + + match ident_str.as_str() { + "fields" => { + let content; + let _parens: syn::token::Paren = parenthesized!(content in input); + + let f: Punctuated = content.parse_terminated(Field::parse, Token![,])?; + fields = f.into_iter().collect(); + }, + _ => return Err(syn::Error::new(ident.span(), "Unknown macro command, expected `fields`")), + } + } + + Ok(Self { + type_path, + generics: syn::Generics::default(), + fields, + }) + + } +} + + +fn gen_match_field_name_arm(simple: &SimpleStruct, default: &proc_macro2::TokenStream, arm_gen: fn(field: &Field) -> proc_macro2::TokenStream) -> proc_macro2::TokenStream { + let field_arms_iter = simple.fields.iter().map(|f| { + let fname = &f.name; + let arm = arm_gen(f); + + quote! { + stringify!(#fname) => #arm + } + }); + + quote! { + match name { + #(#field_arms_iter,)* + _ => #default, + } + } +} + +fn gen_match_field_index_arm(simple: &SimpleStruct, default: &proc_macro2::TokenStream, arm_gen: fn(field: &Field) -> proc_macro2::TokenStream) -> proc_macro2::TokenStream { + let field_arms_iter = simple.fields.iter().enumerate().map(|(idx, f)| { + let arm = arm_gen(f); + + quote! { + #idx => #arm + } + }); + + quote! { + match idx { + #(#field_arms_iter,)* + _ => #default, + } + } +} + +pub(crate) fn impl_reflect_simple_struct(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let simple = syn::parse_macro_input!(input as SimpleStruct); + + let type_path = &simple.type_path; + let (impl_generics, ty_generics, where_clause) = simple.generics.split_for_impl(); + // convert the type path to a string. This would not create a leading separator + /* let type_path_str = { + let idents: Vec = type_path.segments.iter() + .map(|segment| segment.ident.to_string()) + .collect(); + idents.join("::") + }; */ + + let borrow_fn = |field: &Field| { + let name = &field.name; + quote! { + Some(&self.#name) + } + }; + + let borrow_mut_fn = |field: &Field| { + let name = &field.name; + quote! { + Some(&mut self.#name) + } + }; + + let none_default = quote!(None); + let false_return_default = quote!( { return false; }); + + let field_count = simple.fields.len(); + + let field_fn = gen_match_field_name_arm(&simple, &none_default, borrow_fn); + let field_mut_fn = gen_match_field_name_arm(&simple, &none_default, borrow_mut_fn); + let field_at_fn = gen_match_field_index_arm(&simple, &none_default, borrow_fn); + let field_at_mut_fn = gen_match_field_index_arm(&simple, &none_default, borrow_mut_fn); + let field_name_at_fn = gen_match_field_index_arm(&simple, &none_default, |f| { + let name = &f.name; + quote! { + Some(stringify!(#name)) + } + }); + + let set_field_arm = |f: &Field| { + let name = &f.name; + let ty = &f.ty; + quote! { + self.#name = any_val.downcast_ref::<#ty>() + .expect(&format!("Cannot set struct's field of {} type to the provided type of {}", stringify!(#name), val.name())) + .clone() //Some(&#mut_tkn self.#field_ident) + } + }; + + let set_field_fn = gen_match_field_name_arm(&simple, &false_return_default, set_field_arm); + let set_field_at_fn = gen_match_field_index_arm(&simple, &false_return_default, set_field_arm); + + let get_field_ty_arm = |f: &Field| { + let fty = &f.ty; + quote! { + Some(stringify!(#fty)) + } + }; + let field_type_fn = gen_match_field_name_arm(&simple, &none_default, get_field_ty_arm); + let field_type_at_fn = gen_match_field_index_arm(&simple, &none_default, get_field_ty_arm); + + proc_macro::TokenStream::from(quote! { + impl #impl_generics lyra_engine::reflect::Reflect for #type_path #ty_generics #where_clause { + fn name(&self) -> ::std::string::String { + stringify!(#type_path).to_string() + } + + fn type_id(&self) -> std::any::TypeId { + std::any::TypeId::of::<#type_path #ty_generics>() + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + fn apply(&mut self, val: &dyn lyra_engine::reflect::Reflect) { + let val = val.as_any().downcast_ref::() + .expect("The type of `val` is not the same as `self`"); + *self = val.clone(); + } + + fn clone_inner(&self) -> Box { + Box::new(self.clone()) + } + + fn reflect_ref(&self) -> lyra_engine::reflect::ReflectRef { + lyra_engine::reflect::ReflectRef::Value(self) + } + + fn reflect_mut(&mut self) -> lyra_engine::reflect::ReflectMut { + lyra_engine::reflect::ReflectMut::Value(self) + } + + fn reflect_val(&self) -> &dyn lyra_engine::reflect::Reflect { + self + } + + fn reflect_val_mut(&mut self) -> &mut dyn lyra_engine::reflect::Reflect { + self + } + } + + impl #impl_generics lyra_engine::reflect::Struct for #type_path #ty_generics #where_clause { + fn field(&self, name: &str) -> Option<&dyn Reflect> { + #field_fn + } + + fn field_mut(&mut self, name: &str) -> Option<&mut dyn Reflect> { + #field_mut_fn + } + + fn fields_len(&self) -> usize { + #field_count + } + + fn field_at(&self, idx: usize) -> Option<&dyn Reflect> { + #field_at_fn + } + + fn field_at_mut(&mut self, idx: usize) -> Option<&mut dyn Reflect> { + #field_at_mut_fn + } + + fn field_name_at(&self, idx: usize) -> Option<&str> { + #field_name_at_fn + } + + fn set_field(&mut self, name: &str, val: &dyn Reflect) -> bool { + let any_val = val.as_any(); + + #set_field_fn + + true + } + + fn set_field_at(&mut self, idx: usize, val: &dyn Reflect) -> bool { + let any_val = val.as_any(); + + #set_field_at_fn + + true + } + + fn field_type(&self, name: &str) -> Option<&'static str> { + #field_type_fn + } + + fn field_type_at(&self, idx: usize) -> Option<&'static str> { + #field_type_at_fn + } + + fn method(&self, name: &str) -> Option<&Method> { + unimplemented!() + } + + fn method_at(&self, idx: usize) -> Option<&Method> { + unimplemented!() + } + + fn methods_len(&self) -> usize { + unimplemented!() + } + } + }) +} diff --git a/lyra-reflect/src/component.rs b/lyra-reflect/src/component.rs new file mode 100644 index 0000000..a6fb54d --- /dev/null +++ b/lyra-reflect/src/component.rs @@ -0,0 +1,74 @@ +use std::{any::{Any, TypeId}, cell::{Ref, RefMut}}; + +use lyra_ecs::{Component, ComponentInfo, World, Entity, DynamicBundle}; + +use crate::{Reflect, FromType}; + +#[derive(Clone)] +pub struct ReflectedComponent { + pub type_id: TypeId, + pub info: ComponentInfo, + //value: Value, + //from_world: + + //from_world: for<'a> fn (world: &'a mut World) -> Box, + /// Inserts component into entity in the world + fn_insert: for<'a> fn (world: &'a mut World, entity: Entity, component: Box), + /// Inserts component into a bundle + fn_bundle_insert: for<'a> fn (dynamic_bundle: &'a mut DynamicBundle, component: Box), + fn_reflect: for<'a> fn (world: &'a World, entity: Entity) -> Option>, + fn_reflect_mut: for<'a> fn (world: &'a mut World, entity: Entity) -> Option>, +} + +impl ReflectedComponent { + /// Insert the reflected component into an entity. + pub fn insert(&self, world: &mut World, entity: Entity, component: Box) { + (self.fn_insert)(world, entity, component); + } + + /// Insert this component into a DynamicBundle + pub fn bundle_insert(&self, dynamic_bundle: &mut DynamicBundle, component: Box) { + (self.fn_bundle_insert)(dynamic_bundle, component) + } + + /// Retrieves a reflected component from an entity. + pub fn reflect<'a>(&'a self, world: &'a World, entity: Entity) -> Option> { + (self.fn_reflect)(world, entity) + } + + /// Retrieves a reflected component from an entity. + pub fn reflect_mut<'a>(&'a mut self, world: &'a mut World, entity: Entity) -> Option> { + (self.fn_reflect_mut)(world, entity) + } +} + +impl FromType for ReflectedComponent { + fn from_type() -> Self { + ReflectedComponent { + type_id: TypeId::of::(), + info: ComponentInfo::new::(), + fn_insert: |world: &mut World, entity: Entity, component: Box| { + let c = component as Box; + let c = c.downcast::() + .expect("Provided a non-matching type to ReflectedComponent insert method!"); + let c = *c; + world.insert(entity, (c,)); + }, + fn_bundle_insert: |bundle: &mut DynamicBundle, component: Box| { + let c = component as Box; + let c = c.downcast::() + .expect("Provided a non-matching type to ReflectedComponent insert method!"); + let c = *c; + bundle.push(c); + }, + fn_reflect: |world: &World, entity: Entity| { + world.view_one::<&C>(entity) + .get().map(|c| c as Ref) + }, + fn_reflect_mut: |world: &mut World, entity: Entity| { + world.view_one::<&mut C>(entity) + .get().map(|c| c as RefMut) + }, + } + } +} \ No newline at end of file diff --git a/lyra-reflect/src/impls/impl_math.rs b/lyra-reflect/src/impls/impl_math.rs new file mode 100644 index 0000000..2381203 --- /dev/null +++ b/lyra-reflect/src/impls/impl_math.rs @@ -0,0 +1,19 @@ +use lyra_reflect_derive::{impl_reflect_simple_struct, impl_reflect_trait_value}; + +use crate::{lyra_engine, Method, Reflect}; + +impl_reflect_simple_struct!(lyra_math::Vec2, fields(x = f32, y = f32)); +impl_reflect_simple_struct!(lyra_math::Vec3, fields(x = f32, y = f32, z = f32)); +impl_reflect_simple_struct!(lyra_math::Vec4, fields(x = f32, y = f32, z = f32, w = f32)); +impl_reflect_simple_struct!(lyra_math::Quat, fields(x = f32, y = f32, z = f32, w = f32)); + +impl_reflect_simple_struct!( + lyra_math::Transform, + fields( + translation = lyra_math::Vec3, + rotation = lyra_math::Quat, + scale = lyra_math::Vec3 + ) +); + +impl_reflect_trait_value!(lyra_math::Mat4); diff --git a/lyra-reflect/src/impl_std.rs b/lyra-reflect/src/impls/impl_std.rs similarity index 98% rename from lyra-reflect/src/impl_std.rs rename to lyra-reflect/src/impls/impl_std.rs index d756f89..9c135e0 100644 --- a/lyra-reflect/src/impl_std.rs +++ b/lyra-reflect/src/impls/impl_std.rs @@ -4,7 +4,7 @@ use crate::List; use crate::lyra_engine; -use super::{Reflect, ReflectRef, ReflectMut, util}; +use crate::{Reflect, ReflectRef, ReflectMut, util}; impl_reflect_trait_value!(bool); impl_reflect_trait_value!(char); diff --git a/lyra-reflect/src/impls/mod.rs b/lyra-reflect/src/impls/mod.rs new file mode 100644 index 0000000..64af2fb --- /dev/null +++ b/lyra-reflect/src/impls/mod.rs @@ -0,0 +1,4 @@ +pub mod impl_std; + +#[cfg(feature="math")] +pub mod impl_math; \ No newline at end of file diff --git a/lyra-reflect/src/lib.rs b/lyra-reflect/src/lib.rs index ae5c233..0b8437f 100644 --- a/lyra-reflect/src/lib.rs +++ b/lyra-reflect/src/lib.rs @@ -6,6 +6,8 @@ use lyra_ecs::{world::World, DynamicBundle, Component, Entity, ComponentInfo}; extern crate self as lyra_reflect; +pub use lyra_reflect_derive::*; + #[allow(unused_imports)] pub(crate) mod lyra_engine { pub(crate) mod reflect { @@ -34,6 +36,12 @@ pub use dynamic_tuple::*; pub mod reflected_field; pub use reflected_field::*; +pub mod component; +pub use component::*; + +pub mod resource; +pub use resource::*; + pub mod util; pub mod field; pub use field::*; @@ -42,7 +50,7 @@ pub use method::*; pub mod registry; pub use registry::*; -pub mod impl_std; +pub mod impls; pub trait Reflect: Any { fn name(&self) -> String; @@ -235,67 +243,48 @@ pub trait FromType { fn from_type() -> Self; } -#[derive(Clone)] -pub struct ReflectedComponent { - pub type_id: TypeId, - pub info: ComponentInfo, - //value: Value, - //from_world: - - //from_world: for<'a> fn (world: &'a mut World) -> Box, - /// Inserts component into entity in the world - fn_insert: for<'a> fn (world: &'a mut World, entity: Entity, component: &dyn Reflect), - /// Inserts component into a bundle - fn_bundle_insert: for<'a> fn (dynamic_bundle: &'a mut DynamicBundle, component: &dyn Reflect), - fn_reflect: for<'a> fn (world: &'a World, entity: Entity) -> Option>, - fn_reflect_mut: for<'a> fn (world: &'a mut World, entity: Entity) -> Option>, +pub trait ReflectWorldExt { + /// Retrieves the type registry from the world. + fn get_type_registry(&self) -> Ref; + /// Retrieves the type registry mutably from the world. + fn get_type_registry_mut(&self) -> RefMut; + /// Get a registered type from the type registry. Returns `None` if the type is not registered + fn get_type(&self, type_id: TypeId) -> Option>; + /// Get a mutable registered type from the type registry in the world. returns `None` if the type is not registered. + fn get_type_mut(&self, type_id: TypeId) -> Option>; + /// Get a registered type, or register a new type and return it. + fn get_type_or_default(&self, type_id: TypeId) -> RefMut; } -impl ReflectedComponent { - /// Insert the reflected component into an entity. - pub fn insert(&self, world: &mut World, entity: Entity, component: &dyn Reflect) { - (self.fn_insert)(world, entity, component); +impl ReflectWorldExt for World { + fn get_type_registry(&self) -> Ref { + self.get_resource::() } - - /// Insert this component into a DynamicBundle - pub fn bundle_insert(&self, dynamic_bundle: &mut DynamicBundle, component: &dyn Reflect) { - (self.fn_bundle_insert)(dynamic_bundle, component) + + fn get_type_registry_mut(&self) -> RefMut { + self.get_resource_mut::() } - - /// Retrieves a reflected component from an entity. - pub fn reflect<'a>(&'a self, world: &'a World, entity: Entity) -> Option> { - (self.fn_reflect)(world, entity) - } - - /// Retrieves a reflected component from an entity. - pub fn reflect_mut<'a>(&'a mut self, world: &'a mut World, entity: Entity) -> Option> { - (self.fn_reflect_mut)(world, entity) - } -} - -impl FromType for ReflectedComponent { - fn from_type() -> Self { - ReflectedComponent { - type_id: TypeId::of::(), - info: ComponentInfo::new::(), - fn_insert: |world: &mut World, entity: Entity, component: &dyn Reflect| { - let mut c = C::default(); - c.apply(component); - world.insert(entity, (c,)); - }, - fn_bundle_insert: |bundle: &mut DynamicBundle, component: &dyn Reflect| { - let mut c = C::default(); - c.apply(component); - bundle.push(c); - }, - fn_reflect: |world: &World, entity: Entity| { - world.view_one::<&C>(entity) - .get().map(|c| c as Ref) - }, - fn_reflect_mut: |world: &mut World, entity: Entity| { - world.view_one::<&mut C>(entity) - .get().map(|c| c as RefMut) - }, + + fn get_type(&self, type_id: TypeId) -> Option> { + let r = self.get_resource::(); + if r.has_type(type_id) { + Some(Ref::map(r, |tr| tr.get_type(type_id).unwrap())) + } else { + None } } + + fn get_type_mut(&self, type_id: TypeId) -> Option> { + let r = self.get_resource_mut::(); + if r.has_type(type_id) { + Some(RefMut::map(r, |tr| tr.get_type_mut(type_id).unwrap())) + } else { + None + } + } + + fn get_type_or_default(&self, type_id: TypeId) -> RefMut { + let r = self.get_resource_mut::(); + RefMut::map(r, |tr| tr.get_type_or_default(type_id)) + } } \ No newline at end of file diff --git a/lyra-reflect/src/reflected_enum.rs b/lyra-reflect/src/reflected_enum.rs index abbdaf9..ebfed11 100644 --- a/lyra-reflect/src/reflected_enum.rs +++ b/lyra-reflect/src/reflected_enum.rs @@ -52,7 +52,6 @@ pub trait Enum: Reflect { #[allow(unused_variables)] #[cfg(test)] mod tests { - use lyra_reflect_derive::Reflect; use super::EnumType; use crate::{lyra_engine, Reflect, ReflectRef}; diff --git a/lyra-reflect/src/reflected_struct.rs b/lyra-reflect/src/reflected_struct.rs index ce3ec1a..72904ca 100644 --- a/lyra-reflect/src/reflected_struct.rs +++ b/lyra-reflect/src/reflected_struct.rs @@ -41,7 +41,6 @@ pub trait Struct: Reflect { #[cfg(test)] mod tests { - use lyra_reflect_derive::Reflect; use crate::{Reflect, ReflectRef, ReflectMut}; use crate::lyra_engine; diff --git a/lyra-reflect/src/registry.rs b/lyra-reflect/src/registry.rs index 0311f10..46651f4 100644 --- a/lyra-reflect/src/registry.rs +++ b/lyra-reflect/src/registry.rs @@ -21,6 +21,12 @@ impl TypeRegistry { self.inner.get_mut(&type_id) } + /// Get a registered type, or register a new type and return it. + pub fn get_type_or_default(&mut self, type_id: TypeId) -> &mut RegisteredType { + self.inner.entry(type_id) + .or_insert(RegisteredType::default()) + } + pub fn register_type(&mut self) where T: AsRegisteredType + 'static diff --git a/lyra-reflect/src/resource.rs b/lyra-reflect/src/resource.rs new file mode 100644 index 0000000..ab7445c --- /dev/null +++ b/lyra-reflect/src/resource.rs @@ -0,0 +1,63 @@ +use std::{any::{Any, TypeId}, cell::{Ref, RefMut}, ptr::NonNull}; + +use lyra_ecs::{World, ResourceObject}; + +use crate::{Reflect, FromType}; + +#[derive(Clone)] +pub struct ReflectedResource { + pub type_id: TypeId, + + fn_reflect: for<'a> fn (world: &'a World) -> Option>, + fn_reflect_mut: for<'a> fn (world: &'a mut World) -> Option>, + fn_reflect_ptr: fn (world: &mut World) -> Option>, + fn_refl_insert: fn (world: &mut World, this: Box), +} + +impl ReflectedResource { + /// Retrieves the reflected resource from the world. + pub fn reflect<'a>(&self, world: &'a World) -> Option> { + (self.fn_reflect)(world) + } + + /// Retrieves a mutable reflected resource from the world. + pub fn reflect_mut<'a>(&self, world: &'a mut World) -> Option> { + (self.fn_reflect_mut)(world) + } + + pub fn reflect_ptr(&self, world: &mut World) -> Option> { + (self.fn_reflect_ptr)(world) + } + + /// Insert the resource into the world. + pub fn insert(&self, world: &mut World, this: Box) { + (self.fn_refl_insert)(world, this) + } +} + +impl FromType for ReflectedResource { + fn from_type() -> Self { + Self { + type_id: TypeId::of::(), + fn_reflect: |world: &World| { + world.try_get_resource::() + .map(|r| r as Ref) + }, + fn_reflect_mut: |world: &mut World| { + world.try_get_resource_mut::() + .map(|r| r as RefMut) + }, + fn_reflect_ptr: |world: &mut World| unsafe { + world.try_get_resource_ptr::() + .map(|ptr| ptr.cast::()) + }, + fn_refl_insert: |world: &mut World, this: Box| { + let res = this as Box; + let res = res.downcast::() + .expect("Provided a non-matching type to ReflectedResource insert method!"); + let res = *res; + world.add_resource(res); + } + } + } +} \ No newline at end of file diff --git a/lyra-resource/Cargo.toml b/lyra-resource/Cargo.toml index bbc45e8..753caff 100644 --- a/lyra-resource/Cargo.toml +++ b/lyra-resource/Cargo.toml @@ -6,15 +6,19 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +lyra-ecs = { path = "../lyra-ecs" } anyhow = "1.0.75" base64 = "0.21.4" -edict = "0.5.0" +crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] } glam = "0.24.1" gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness", "KHR_materials_specular"] } image = "0.24.7" # not using custom matcher, or file type from file path infer = { version = "0.15.0", default-features = false } mime = "0.3.17" +notify = "6.1.1" +notify-debouncer-full = "0.3.1" +#notify = { version = "6.1.1", default-features = false, features = [ "fsevent-sys", "macos_fsevent" ]} # disables crossbeam-channel percent-encoding = "2.3.0" thiserror = "1.0.48" tracing = "0.1.37" diff --git a/lyra-resource/src/lib.rs b/lyra-resource/src/lib.rs index f78b8ab..0427c2f 100644 --- a/lyra-resource/src/lib.rs +++ b/lyra-resource/src/lib.rs @@ -16,4 +16,17 @@ pub use model::*; pub mod material; pub use material::*; -pub(crate) mod util; \ No newline at end of file +pub mod world_ext; +pub use world_ext::*; + +pub(crate) mod util; + +pub use crossbeam::channel as channel; +pub use notify; + +#[allow(unused_imports)] +pub(crate) mod lyra_engine { + pub(crate) mod ecs { + pub use lyra_ecs::*; + } +} diff --git a/lyra-resource/src/loader/image.rs b/lyra-resource/src/loader/image.rs index 8a1940b..d7c3df7 100644 --- a/lyra-resource/src/loader/image.rs +++ b/lyra-resource/src/loader/image.rs @@ -2,7 +2,7 @@ use std::{fs::File, sync::Arc, io::Read}; use image::ImageError; -use crate::{resource_manager::ResourceStorage, texture::Texture, resource::Resource, ResourceManager}; +use crate::{resource_manager::ResourceStorage, texture::Texture, resource::ResHandle, ResourceManager}; use super::{LoaderError, ResourceLoader}; @@ -62,7 +62,7 @@ impl ResourceLoader for ImageLoader { let texture = Texture { image, }; - let res = Resource::with_data(path, texture); + let res = ResHandle::with_data(path, texture); Ok(Arc::new(res)) } @@ -76,7 +76,7 @@ impl ResourceLoader for ImageLoader { let texture = Texture { image, }; - let res = Resource::with_data(&uuid::Uuid::new_v4().to_string(), texture); + let res = ResHandle::with_data(&uuid::Uuid::new_v4().to_string(), texture); Ok(Arc::new(res)) } diff --git a/lyra-resource/src/loader/mod.rs b/lyra-resource/src/loader/mod.rs index 63fef55..a4e9e64 100644 --- a/lyra-resource/src/loader/mod.rs +++ b/lyra-resource/src/loader/mod.rs @@ -30,7 +30,7 @@ impl From for LoaderError { } pub trait ResourceLoader { - /// Returns the extensions that this loader supports. + /// Returns the extensions that this loader supports. Does not include the `.` fn extensions(&self) -> &[&str]; /// Returns the mime types that this loader supports. fn mime_types(&self) -> &[&str]; diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index c9caf4b..396eae5 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -2,7 +2,7 @@ use std::{sync::Arc, path::PathBuf}; use thiserror::Error; -use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, MeshIndices, ResourceManager, util}; +use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, ResHandle, Material, MeshIndices, ResourceManager, util}; use tracing::debug; @@ -189,7 +189,7 @@ impl ResourceLoader for ModelLoader { .collect(); debug!("Loaded {} meshes, and {} materials from '{}'", meshes.len(), materials.len(), path); - Ok(Arc::new(Resource::with_data(path, Model::new(meshes)))) + Ok(Arc::new(ResHandle::with_data(path, Model::new(meshes)))) } #[allow(unused_variables)] @@ -216,8 +216,8 @@ mod tests { let mut manager = ResourceManager::new(); let loader = ModelLoader::default(); let model = loader.load(&mut manager, &path).unwrap(); - let model = Arc::downcast::>(model.as_arc_any()).unwrap(); - let model = model.data.as_ref().unwrap(); + let model = Arc::downcast::>(model.as_arc_any()).unwrap(); + let model = model.data_ref(); assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh let mesh = &model.meshes[0]; assert!(mesh.position().unwrap().len() > 0); diff --git a/lyra-resource/src/model.rs b/lyra-resource/src/model.rs index f1c24c2..acc8c2e 100644 --- a/lyra-resource/src/model.rs +++ b/lyra-resource/src/model.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use crate::Material; +use crate::lyra_engine; #[repr(C)] #[derive(Clone, Debug, PartialEq)] @@ -81,7 +82,7 @@ pub enum MeshVertexAttribute { Other(String), } -#[derive(Clone, edict::Component)] +#[derive(Clone, lyra_ecs::Component)] pub struct Mesh { pub uuid: uuid::Uuid, pub attributes: HashMap, diff --git a/lyra-resource/src/resource.rs b/lyra-resource/src/resource.rs index 2dbb2a6..1423de2 100644 --- a/lyra-resource/src/resource.rs +++ b/lyra-resource/src/resource.rs @@ -1,32 +1,174 @@ -use std::sync::Arc; +use std::{any::Any, sync::{Arc, RwLock}}; use uuid::Uuid; +use crate::ResourceStorage; + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum ResourceState { Loading, Ready, } -#[derive(Clone)] -pub struct Resource { - pub path: String, - pub data: Option>, - pub uuid: Uuid, - pub state: ResourceState, +pub struct ResourceDataRef<'a, T> { + guard: std::sync::RwLockReadGuard<'a, Resource>, } -/// A helper type to make it easier to use resources -pub type ResHandle = Arc>; +impl<'a, T> std::ops::Deref for ResourceDataRef<'a, T> { + type Target = T; -impl Resource { + fn deref(&self) -> &Self::Target { + // safety: this struct must only be created if the resource is loaded + self.guard.data.as_ref().unwrap() + } +} + +pub struct Resource { + path: String, + pub(crate) data: Option, + pub(crate) version: usize, + pub(crate) state: ResourceState, + uuid: Uuid, + pub(crate) is_watched: bool, +} + +/// A handle to a resource. +/// +/// # Note +/// This struct has an inner [`RwLock`] to the resource data, so most methods may be blocking. +/// However, the only times it will be blocking is if another thread is reloading the resource +/// and has a write lock on the data. This means that most of the time, it is not blocking. +pub struct ResHandle { + pub(crate) data: Arc>>, +} + +impl Clone for ResHandle { + fn clone(&self) -> Self { + Self { data: self.data.clone() } + } +} + +impl ResHandle { /// Create the resource with data, its assumed the state is `Ready` pub fn with_data(path: &str, data: T) -> Self { - Self { + let res_version = Resource { path: path.to_string(), - data: Some(Arc::new(data)), - uuid: Uuid::new_v4(), + data: Some(data), + version: 0, state: ResourceState::Ready, + uuid: Uuid::new_v4(), + is_watched: false, + }; + + Self { + data: Arc::new(RwLock::new(res_version)), } } + + /// Returns a boolean indicating if this resource's path is being watched. + pub fn is_watched(&self) -> bool { + let d = self.data.read().expect("Resource mutex was poisoned!"); + d.is_watched + } + + /// Returns a boolean indicating if this resource is loaded + pub fn is_loaded(&self) -> bool { + let d = self.data.read().expect("Resource mutex was poisoned!"); + d.state == ResourceState::Ready + } + + /// Returns the current state of the resource. + pub fn state(&self) -> ResourceState { + let d = self.data.read().expect("Resource mutex was poisoned!"); + d.state + } + + /// Returns the path that the resource was loaded from. + pub fn path(&self) -> String { + let d = self.data.read().expect("Resource mutex was poisoned!"); + d.path.to_string() + } + + /// Returns the uuid of the resource. + pub fn uuid(&self) -> Uuid { + let d = self.data.read().expect("Resource mutex was poisoned!"); + d.uuid + } + + /// Retrieves the current version of the resource. This gets incremented when the resource + /// is reloaded. + pub fn version(&self) -> usize { + let d = self.data.read().expect("Resource mutex was poisoned!"); + d.version + } + + /// Get a reference to the data in the resource + /// + /// # Panics + /// Panics if the resource was not loaded yet. + pub fn data_ref<'a>(&'a self) -> ResourceDataRef<'a, T> { + let d = self.data.read().expect("Resource mutex was poisoned!"); + ResourceDataRef { + guard: d + } + } + + /// Attempt to get a borrow to the resource data. Returns `None` if the resource is not loaded. + pub fn try_data_ref<'a>(&'a self) -> Option> { + if self.is_loaded() { + let d = self.data.read().expect("Resource mutex was poisoned!"); + Some(ResourceDataRef { + guard: d + }) + } else { + None + } + } +} + +impl ResourceStorage for ResHandle { + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn as_arc_any(self: Arc) -> Arc { + self.clone() + } + + fn as_box_any(self: Box) -> Box { + self + } + + fn set_watched(&self, watched: bool) { + let mut w = self.data.write().unwrap(); + w.is_watched = watched; + } + + fn path(&self) -> String { + self.path() + } + + fn version(&self) -> usize { + self.version() + } + + fn state(&self) -> ResourceState { + self.state() + } + + fn uuid(&self) -> Uuid { + self.uuid() + } + + fn is_watched(&self) -> bool { + self.is_watched() + } + + fn is_loaded(&self) -> bool { + self.is_loaded() + } } \ No newline at end of file diff --git a/lyra-resource/src/resource_manager.rs b/lyra-resource/src/resource_manager.rs index 0224269..dc24d33 100644 --- a/lyra-resource/src/resource_manager.rs +++ b/lyra-resource/src/resource_manager.rs @@ -1,28 +1,30 @@ -use std::{sync::Arc, collections::HashMap, any::Any}; +use std::{sync::{Arc, RwLock}, collections::HashMap, any::Any, path::Path, time::Duration}; +use crossbeam::channel::Receiver; +use notify::{Watcher, RecommendedWatcher}; +use notify_debouncer_full::{DebouncedEvent, FileIdMap}; use thiserror::Error; +use uuid::Uuid; -use crate::{resource::Resource, loader::{ResourceLoader, LoaderError, image::ImageLoader, model::ModelLoader}}; +use crate::{loader::{image::ImageLoader, model::ModelLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState}; +/// A trait for type erased storage of a resource. +/// Implemented for [`ResHandle`] pub trait ResourceStorage: Send + Sync + Any + 'static { fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; fn as_arc_any(self: Arc) -> Arc; -} + fn as_box_any(self: Box) -> Box; + /// Do not set a resource to watched if it is not actually watched. + /// This is used internally. + fn set_watched(&self, watched: bool); -/// Implements this trait for anything that fits the type bounds -impl ResourceStorage for T { - fn as_any(&self) -> &dyn Any { - self - } - - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - - fn as_arc_any(self: Arc) -> Arc { - self.clone() - } + fn path(&self) -> String; + fn version(&self) -> usize; + fn state(&self) -> ResourceState; + fn uuid(&self) -> Uuid; + fn is_watched(&self) -> bool; + fn is_loaded(&self) -> bool; } #[derive(Error, Debug)] @@ -46,9 +48,16 @@ impl From for RequestError { /// A struct that stores all Manager data. This is requried for sending //struct ManagerStorage +/// A struct that +pub struct ResourceWatcher { + debouncer: Arc>>, + events_recv: Receiver, Vec>>, +} + pub struct ResourceManager { resources: HashMap>, loaders: Vec>, + watchers: HashMap, } impl Default for ResourceManager { @@ -62,6 +71,7 @@ impl ResourceManager { Self { resources: HashMap::new(), loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ], + watchers: HashMap::new(), } } @@ -73,11 +83,15 @@ impl ResourceManager { self.loaders.push(Arc::new(L::default())); } - pub fn request(&mut self, path: &str) -> Result>, RequestError> { + pub fn request(&mut self, path: &str) -> Result, RequestError> + where + T: Send + Sync + Any + 'static + { match self.resources.get(&path.to_string()) { Some(res) => { let res = res.clone().as_arc_any(); - let res = res.downcast::>().expect("Failure to downcast resource"); + let res: Arc> = res.downcast::>().expect("Failure to downcast resource"); + let res = ResHandle::::clone(&res); Ok(res) }, @@ -88,12 +102,43 @@ impl ResourceManager { // Load the resource and store it let loader = Arc::clone(loader); // stop borrowing from self let res = loader.load(self, path)?; + let res: Arc = Arc::from(res); self.resources.insert(path.to_string(), res.clone()); // cast Arc to Arc let res = res.as_arc_any(); - let res = res.downcast::>() + let res = res.downcast::>() .expect("Failure to downcast resource! Does the loader return an `Arc>`?"); + let res = ResHandle::::clone(&res); + + Ok(res) + } else { + Err(RequestError::UnsupportedFileExtension(path.to_string())) + } + } + } + } + + /// Request a resource without downcasting to a ResHandle. + /// Whenever you're ready to downcast, you can do so like this: + /// ```compile_fail + /// let arc_any = res_arc.as_arc_any(); + /// let res: Arc> = res.downcast::>().expect("Failure to downcast resource"); + /// ``` + pub fn request_raw(&mut self, path: &str) -> Result, RequestError> { + match self.resources.get(&path.to_string()) { + Some(res) => { + Ok(res.clone()) + }, + None => { + if let Some(loader) = self.loaders.iter() + .find(|l| l.does_support_file(path)) { + + // Load the resource and store it + let loader = Arc::clone(loader); // stop borrowing from self + let res = loader.load(self, path)?; + let res: Arc = Arc::from(res); + self.resources.insert(path.to_string(), res.clone()); Ok(res) } else { @@ -112,18 +157,23 @@ impl ResourceManager { /// * `bytes` - The bytes to store. /// /// Returns: The `Arc` to the now stored resource - pub fn load_bytes(&mut self, ident: &str, mime_type: &str, bytes: Vec, offset: usize, length: usize) -> Result>, RequestError> { + pub fn load_bytes(&mut self, ident: &str, mime_type: &str, bytes: Vec, offset: usize, length: usize) -> Result, RequestError> + where + T: Send + Sync + Any + 'static + { if let Some(loader) = self.loaders.iter() .find(|l| l.does_support_mime(mime_type)) { let loader = loader.clone(); let res = loader.load_bytes(self, bytes, offset, length)?; + let res: Arc = Arc::from(res); self.resources.insert(ident.to_string(), res.clone()); // code here... // cast Arc to Arc let res = res.as_arc_any(); - let res = res.downcast::>() + let res = res.downcast::>() .expect("Failure to downcast resource! Does the loader return an `Arc>`?"); + let res = ResHandle::::clone(&res); Ok(res) } else { @@ -132,11 +182,14 @@ impl ResourceManager { } /// Requests bytes from the manager. - pub fn request_loaded_bytes(&mut self, ident: &str) -> Result>, RequestError> { + pub fn request_loaded_bytes(&mut self, ident: &str) -> Result>, RequestError> + where + T: Send + Sync + Any + 'static + { match self.resources.get(&ident.to_string()) { Some(res) => { let res = res.clone().as_arc_any(); - let res = res.downcast::>().expect("Failure to downcast resource"); + let res = res.downcast::>().expect("Failure to downcast resource"); Ok(res) }, @@ -145,6 +198,91 @@ impl ResourceManager { } } } + + /// Start watching a path for changes. Returns a mspc channel that will send events + pub fn watch(&mut self, path: &str, recursive: bool) -> notify::Result, Vec>>> { + let (send, recv) = crossbeam::channel::bounded(15); + let mut watcher = notify_debouncer_full::new_debouncer(Duration::from_millis(1000), None, send)?; + + let recurse_mode = match recursive { + true => notify::RecursiveMode::Recursive, + false => notify::RecursiveMode::NonRecursive, + }; + watcher.watcher().watch(path.as_ref(), recurse_mode)?; + + let watcher = Arc::new(RwLock::new(watcher)); + let watcher = ResourceWatcher { + debouncer: watcher, + events_recv: recv.clone(), + }; + + self.watchers.insert(path.to_string(), watcher); + + let res = self.resources.get(&path.to_string()) + .expect("The path that was watched has not been loaded as a resource yet"); + res.set_watched(true); + + Ok(recv) + } + + /// Stops watching a path + pub fn stop_watching(&mut self, path: &str) -> notify::Result<()> { + if let Some(watcher) = self.watchers.get(path) { + let mut watcher = watcher.debouncer.write().unwrap(); + watcher.watcher().unwatch(Path::new(path))?; + + // unwrap is safe since only loaded resources can be watched + let res = self.resources.get(&path.to_string()).unwrap(); + res.set_watched(false); + } + + Ok(()) + } + + /// Returns a mspc receiver for watcher events of a specific path. The path must already + /// be watched with [`ResourceManager::watch`] for this to return `Some`. + pub fn watcher_event_recv(&self, path: &str) -> Option, Vec>>> { + self.watchers.get(&path.to_string()) + .map(|w| w.events_recv.clone()) + } + + /// Reloads a resource. The data inside the resource will be updated, the state may + pub fn reload(&mut self, resource: ResHandle) -> Result<(), RequestError> + where + T: Send + Sync + Any + 'static + { + let path = resource.path(); + if let Some(loader) = self.loaders.iter() + .find(|l| l.does_support_file(&path)) { + println!("got loader"); + let loader = Arc::clone(loader); // stop borrowing from self + let loaded = loader.load(self, &path)?; + let loaded = loaded.as_arc_any(); + + let loaded = loaded.downcast::>() + .unwrap(); + let loaded = match Arc::try_unwrap(loaded) { + Ok(v) => v, + Err(_) => panic!("Seems impossible that this would happen, the resource was just loaded!"), + }; + let loaded = loaded.data; + let loaded = match Arc::try_unwrap(loaded) { + Ok(v) => v, + Err(_) => panic!("Seems impossible that this would happen, the resource was just loaded!"), + }; + let loaded = loaded.into_inner().unwrap(); + + let res_lock = &resource.data; + let mut res_lock = res_lock.write().unwrap(); + let version = res_lock.version; + + res_lock.data = loaded.data; + res_lock.state = loaded.state; + res_lock.version = version + 1; + } + + Ok(()) + } } #[cfg(test)] @@ -165,20 +303,20 @@ mod tests { fn load_image() { let mut man = ResourceManager::new(); let res = man.request::(&get_image("squiggles.png")).unwrap(); - assert_eq!(res.state, ResourceState::Ready); - let img = res.data.as_ref(); + assert_eq!(res.state(), ResourceState::Ready); + let img = res.try_data_ref(); img.unwrap(); } - /// Ensures that only one copy of the same thing made + /// Ensures that only one copy of the same data was made #[test] fn ensure_single() { let mut man = ResourceManager::new(); let res = man.request::(&get_image("squiggles.png")).unwrap(); - assert_eq!(Arc::strong_count(&res), 2); + assert_eq!(Arc::strong_count(&res.data), 2); let resagain = man.request::(&get_image("squiggles.png")).unwrap(); - assert_eq!(Arc::strong_count(&resagain), 3); + assert_eq!(Arc::strong_count(&resagain.data), 3); } /// Ensures that an error is returned when a file that doesn't exist is requested @@ -196,4 +334,42 @@ mod tests { } ); } + + #[test] + fn reload_image() { + let mut man = ResourceManager::new(); + let res = man.request::(&get_image("squiggles.png")).unwrap(); + assert_eq!(res.state(), ResourceState::Ready); + let img = res.try_data_ref(); + img.unwrap(); + + println!("Path = {}", res.path()); + man.reload(res.clone()).unwrap(); + assert_eq!(res.version(), 1); + + man.reload(res.clone()).unwrap(); + assert_eq!(res.version(), 2); + } + + #[test] + fn watch_image() { + let orig_path = get_image("squiggles.png"); + let image_path = get_image("squiggles_test.png"); + std::fs::copy(orig_path, &image_path).unwrap(); + + let mut man = ResourceManager::new(); + let res = man.request::(&image_path).unwrap(); + assert_eq!(res.state(), ResourceState::Ready); + let img = res.try_data_ref(); + img.unwrap(); + + let recv = man.watch(&image_path, false).unwrap(); + + std::fs::remove_file(&image_path).unwrap(); + + let event = recv.recv().unwrap(); + let event = event.unwrap(); + + assert!(event.iter().any(|ev| ev.kind.is_remove() || ev.kind.is_modify())); + } } \ No newline at end of file diff --git a/lyra-resource/src/world_ext.rs b/lyra-resource/src/world_ext.rs new file mode 100644 index 0000000..2db2b04 --- /dev/null +++ b/lyra-resource/src/world_ext.rs @@ -0,0 +1,75 @@ +use std::any::Any; + +use crossbeam::channel::Receiver; +use lyra_ecs::World; +use notify_debouncer_full::DebouncedEvent; + +use crate::{RequestError, ResHandle, ResourceLoader, ResourceManager}; + +pub trait WorldAssetExt { + /// Register a resource loader with the resource manager. + fn register_res_loader(&mut self) + where + L: ResourceLoader + Default + 'static; + + /// Request a resource from the resource manager. + fn request_res(&mut self, path: &str) -> Result, RequestError> + where + T: Send + Sync + Any + 'static; + + /// Start watching a resource for changes. Returns a crossbeam channel that can be used to listen for events. + fn watch_res(&mut self, path: &str, recursive: bool) -> notify::Result, Vec>>>; + + /// Stop watching a resource for changes. + fn stop_watching_res(&mut self, path: &str) -> notify::Result<()>; + + /// Try to retrieve a crossbeam channel for a path that is currently watched. Returns None if the path is not watched + fn res_watcher_recv(&self, path: &str) -> Option, Vec>>>; + + /// Reload a resource. The data will be updated in the handle. This is not + /// automatically triggered if the resource is being watched. + fn reload_res(&mut self, resource: ResHandle) -> Result<(), RequestError> + where + T: Send + Sync + Any + 'static; +} + +impl WorldAssetExt for World { + fn register_res_loader(&mut self) + where + L: ResourceLoader + Default + 'static + { + let mut man = self.get_resource_or_default::(); + man.register_loader::(); + } + + fn request_res(&mut self, path: &str) -> Result, RequestError> + where + T: Send + Sync + Any + 'static + { + let mut man = self.get_resource_or_default::(); + man.request(path) + } + + fn watch_res(&mut self, path: &str, recursive: bool) -> notify::Result, Vec>>> { + let mut man = self.get_resource_or_default::(); + man.watch(path, recursive) + } + + fn stop_watching_res(&mut self, path: &str) -> notify::Result<()> { + let mut man = self.get_resource_or_default::(); + man.stop_watching(path) + } + + fn res_watcher_recv(&self, path: &str) -> Option, Vec>>> { + let man = self.get_resource::(); + man.watcher_event_recv(path) + } + + fn reload_res(&mut self, resource: ResHandle) -> Result<(), RequestError> + where + T: Send + Sync + Any + 'static + { + let mut man = self.get_resource_or_default::(); + man.reload(resource) + } +} \ No newline at end of file diff --git a/lyra-scripting/Cargo.toml b/lyra-scripting/Cargo.toml index 4e2aec6..22d2bd0 100644 --- a/lyra-scripting/Cargo.toml +++ b/lyra-scripting/Cargo.toml @@ -7,11 +7,13 @@ edition = "2021" [features] default = ["lua"] -lua = ["dep:mlua"] +lua = ["dep:elua"] +teal = ["lua", "elua/teal"] [dependencies] -lyra-ecs = { path = "../lyra-ecs" } -lyra-reflect = { path = "../lyra-reflect" } +lyra-scripting-derive = { path = "lyra-scripting-derive" } +lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } +lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] } lyra-resource = { path = "../lyra-resource" } lyra-game = { path = "../lyra-game" } thiserror = "1.0.50" @@ -19,8 +21,10 @@ anyhow = "1.0.77" tracing = "0.1.37" # enabled with lua feature -mlua = { version = "0.9.2", features = ["lua54"], optional = true } # luajit maybe? +#mlua = { version = "0.9.2", features = ["lua54"], optional = true } # luajit maybe? +elua = { path = "./elua", optional = true } +itertools = "0.12.0" [dev-dependencies] -tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] } \ No newline at end of file +tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] } diff --git a/lyra-scripting/elua b/lyra-scripting/elua new file mode 160000 index 0000000..54c9926 --- /dev/null +++ b/lyra-scripting/elua @@ -0,0 +1 @@ +Subproject commit 54c9926a04cdef657289fd67730c0b85d1bdda3e diff --git a/lyra-scripting/lyra-scripting-derive/Cargo.toml b/lyra-scripting/lyra-scripting-derive/Cargo.toml new file mode 100644 index 0000000..9595a9d --- /dev/null +++ b/lyra-scripting/lyra-scripting-derive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "lyra-scripting-derive" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.70" +quote = "1.0.33" +syn = "2.0.41" diff --git a/lyra-scripting/lyra-scripting-derive/src/lib.rs b/lyra-scripting/lyra-scripting-derive/src/lib.rs new file mode 100644 index 0000000..cb7d832 --- /dev/null +++ b/lyra-scripting/lyra-scripting-derive/src/lib.rs @@ -0,0 +1,723 @@ +use proc_macro2::{Ident, Span}; +use quote::quote; +use syn::{parse_macro_input, Path, Token, token, parenthesized, punctuated::Punctuated, braced}; + +mod mat_wrapper; +use mat_wrapper::MatWrapper; + +const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type"; +const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect"; + +pub(crate) struct MetaMethod { + pub ident: Ident, + pub mods: Vec, +} + +impl syn::parse::Parse for MetaMethod { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let ident: Ident = input.parse()?; + + let mods = if input.peek(token::Paren) { + let content; + let _parens: token::Paren = parenthesized!(content in input); + content.parse_terminated(Ident::parse, Token![,])? + .into_iter().collect() + } else { vec![] }; + + Ok(Self { + ident, + mods, + }) + } +} + +impl MetaMethod { + /// Returns a boolean if an identifier is a lua wrapper, and therefore also userdata + fn is_arg_wrapper(ident: &Ident) -> bool { + let s = ident.to_string(); + s.starts_with("Lua") + } + + /// Returns a boolean indiciating if the metamethod has takes in any arguments + fn does_metamethod_have_arg(metamethod: &Ident) -> bool { + let mm_str = metamethod.to_string(); + let mm_str = mm_str.as_str(); + match mm_str { + "Add" | "Sub" | "Div" | "Mul" | "Mod" | "Eq" | "Shl" | "Shr" | "BAnd" | "BOr" + | "BXor" => { + true + }, + "Unm" | "BNot" | "ToString" => { + false + }, + _ => todo!(), + } + } + + /// returns the tokens of the body of the metamethod + /// + /// Parameters + /// * `metamethod` - The ident of the metamethod that is being implemented. + /// * `other` - The tokens of the argument used in the metamethod. + fn get_method_body(metamethod: &Ident, other: proc_macro2::TokenStream) -> proc_macro2::TokenStream { + let mm_str = metamethod.to_string(); + let mm_str = mm_str.as_str(); + match mm_str { + "Add" | "Sub" | "Div" | "Mul" | "Mod" => { + let symbol = match mm_str { + "Add" => quote!(+), + "Sub" => quote!(-), + "Div" => quote!(/), + "Mul" => quote!(*), + "Mod" => quote!(%), + _ => unreachable!(), // the string was just checked to be one of these + }; + + quote! { + Ok(Self(this.0 #symbol #other)) + } + }, + "Unm" => { + quote! { + Ok(Self(-this.0)) + } + }, + "Eq" => { + quote! { + Ok(this.0 == #other) + } + }, + "Shl" => { + quote! { + Ok(Self(this.0 << #other)) + } + } + "Shr" => { + quote! { + Ok(Self(this.0 >> #other)) + } + }, + "BAnd" | "BOr" | "BXor" => { + let symbol = match mm_str { + "BAnd" => { + quote!(&) + }, + "BOr" => { + quote!(|) + }, + "BXor" => { + quote!(^) + }, + _ => unreachable!() // the string was just checked to be one of these + }; + + quote! { + Ok(Self(this.0 #symbol #other)) + } + }, + "BNot" => { + quote! { + Ok(Self(!this.0)) + } + }, + "ToString" => { + quote! { + Ok(format!("{:?}", this.0)) + } + }, + _ => syn::Error::new_spanned(metamethod, + "unsupported auto implementation of metamethod").to_compile_error(), + } + } + + fn get_body_for_arg(mt_ident: &Ident, arg_ident: &Ident, arg_param: proc_macro2::TokenStream) -> proc_macro2::TokenStream { + let other: proc_macro2::TokenStream = if Self::is_arg_wrapper(arg_ident) { + // Lua wrappers must be dereferenced + quote! { + #arg_param.0 + } + } else { + quote! { + #arg_param + } + }; + Self::get_method_body(&mt_ident, other) + } + + pub fn to_tokens(&self, wrapper_ident: &Ident) -> proc_macro2::TokenStream { + let wrapped_str = &wrapper_ident.to_string()[3..]; // removes starting 'Lua' from name + let mt_ident = &self.ident; + let mt_lua_name = mt_ident.to_string().to_lowercase(); + + if self.mods.is_empty() { + let other = quote! { + v.0 + }; + let body = Self::get_method_body(&self.ident, other); + + if Self::does_metamethod_have_arg(&self.ident) { + quote! { + builder.meta_method(elua::MetaMethod::#mt_ident, |_, this, (v,): (#wrapper_ident,)| { + #body + }); + } + } else { + quote! { + builder.meta_method(elua::MetaMethod::#mt_ident, |_, this, ()| { + #body + }); + } + } + + } else if self.mods.len() == 1 { + let first = self.mods.iter().next().unwrap(); + let body = Self::get_body_for_arg(&self.ident, first, quote!(v)); + + quote! { + builder.meta_method(elua::MetaMethod::#mt_ident, |_, this, (v,): (#first,)| { + #body + }); + } + } else { + // an optional match arm that matches elua::Value:Number + let number_arm = { + let num_ident = self.mods.iter().find(|i| { + let is = i.to_string(); + let is = is.as_str(); + match is { + "u8" | "u16" | "u32" | "u64" | "u128" + | "i8" | "i16" | "i32" | "i64" | "i128" + | "f32" | "f64" => true, + _ => false, + } + }); + + if let Some(num_ident) = num_ident { + let body = Self::get_body_for_arg(&self.ident, num_ident, quote!(n as #num_ident)); + + quote! { + elua::Value::Number(n) => { + #body + }, + } + } else { quote!() } + }; + + let userdata_arm = { + let wrappers: Vec<&Ident> = self.mods.iter() + .filter(|i| Self::is_arg_wrapper(i)) + .collect(); + + let if_statements = wrappers.iter().map(|i| { + let body = Self::get_method_body(&self.ident, quote!(other.0)); + + quote! { + if let Ok(other) = ud.as_ref::<#i>() { + #body + } + } + + }); + + quote! { + elua::Value::Userdata(ud) => { + #(#if_statements else)* + // this is the body of the else statement + { + // try to get the name of the userdata for the error message + if let Ok(mt) = ud.get_metatable() { + if let Ok(name) = mt.get::<_, String>("__name") { + return Err(elua::Error::BadArgument { + func: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)), + arg_index: 2, + arg_name: Some("rhs".to_string()), + error: Arc::new(elua::Error::Runtime( + format!("cannot multiply with unknown userdata named {}", name) + )) + }); + } + } + + Err(elua::Error::BadArgument { + func: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)), + arg_index: 2, + arg_name: Some("rhs".to_string()), + error: Arc::new( + elua::Error::runtime("cannot multiply with unknown userdata") + ) + }) + } + }, + } + }; + + quote! { + builder.meta_method(elua::MetaMethod::#mt_ident, |_, this, (v,): (elua::Value,)| { + match v { + #number_arm + #userdata_arm + _ => Err(elua::Error::BadArgument { + func: Some(format!("{}.__{}", #wrapped_str, #mt_lua_name)), + arg_index: 2, + arg_name: Some("rhs".to_string()), + error: Arc::new( + elua::Error::Runtime(format!("cannot multiply with {}", v.type_name())) + ) + }) + } + }); + } + } + } +} + +pub(crate) struct VecWrapper { + +} + +impl VecWrapper { + fn vec_size(&self, wrapper_ident: &Ident) -> usize { + let name = wrapper_ident.to_string(); + name[name.len() - 1..].parse::() + .or_else(|_| name[name.len() - 2.. name.len() - 1].parse::()) + .expect("Failure to grab Vec size from ident name") + } + + /// Returns the token stream of the type of the axis of the vec (Vec2 vs IVec2 vs I64Vec2, etc.) + fn vec_axis_type(&self, wrapper_ident: &Ident) -> &'static str { + let name = wrapper_ident.to_string(); + let start = name.find("Vec").unwrap(); + + let before = &name[start - 1.. start]; + match before { + "D" => return "f64", + "I" => return "i32", + "U" => return "u32", + "B" => return "bool", + _ => {}, + } + //println!("before is {before}"); + + let three_before = &name[start - 3.. start]; + match three_before { + "I64" => return "i64", + "U64" => return "u64", + _ => {}, + } + //println!("three before is {three_before}"); + + "f32" + } + + pub fn to_field_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream { + let mut consts = vec![quote!(ZERO), quote!(ONE), quote!(X), + quote!(Y), ]; // , quote!(AXES) + + let vec_size = self.vec_size(wrapper_ident); + let axis_type_name = self.vec_axis_type(wrapper_ident); + + if axis_type_name.contains("b") { + return quote! { + builder.field("FALSE", #wrapper_ident(#wrapped_path::FALSE)); + builder.field("TRUE", #wrapper_ident(#wrapped_path::TRUE)); + }; + } + + if vec_size >= 3 { + consts.push(quote!(Z)); + + // no negative numbers for unsigned vecs + if !axis_type_name.contains("u") { + consts.push(quote!(NEG_Z)); + } + } + + if vec_size == 4 { + consts.push(quote!(W)); + + // no negative numbers for unsigned vecs + if !axis_type_name.contains("u") { + consts.push(quote!(NEG_W)); + } + } + + // no negative numbers for unsigned vecs + if !axis_type_name.contains("u") { + consts.push(quote!(NEG_X)); + consts.push(quote!(NEG_Y)); + consts.push(quote!(NEG_ONE)); + } + + if axis_type_name.contains("f") { + consts.push(quote!(NAN)) + } + + let const_tokens = consts.iter().map(|cnst| { + let const_name = cnst.to_string(); + + quote! { + builder.field(#const_name, #wrapper_ident(#wrapped_path::#cnst)); + } + }); + + quote! { + #(#const_tokens)* + } + } + + pub fn to_method_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream { + let vec_size = self.vec_size(wrapper_ident); + let axis_type_name = self.vec_axis_type(wrapper_ident); + // methods that only some vecs have + let mut optional_methods = vec![]; + + // boolean vectors dont have much :( + if axis_type_name.contains("b") { + return quote!(); // TODO: all, any, bitmask, splat + } + + if axis_type_name.contains("f") { + let type_id = Ident::new(axis_type_name, Span::call_site()); + + optional_methods.push( + quote! { + builder.method("clamp_length", + |_, this, (min, max): (#type_id, #type_id)| { + Ok(#wrapper_ident(this.clamp_length(min, max))) + }); + + builder.method("abs_diff_eq", + |_, this, (rhs, max_abs_diff): (#wrapper_ident, #type_id)| { + Ok(this.abs_diff_eq(rhs.0, max_abs_diff)) + }); + + builder.method("ceil", + |_, this, (): ()| { + Ok(#wrapper_ident(this.ceil())) + }); + } + ); + + if vec_size != 4 { + optional_methods.push( + quote! { + builder.method("angle_between", + |_, this, (rhs,): (#wrapper_ident,)| { + Ok(this.angle_between(rhs.0)) + }); + } + ) + } + } + + if !axis_type_name.contains("u") { + optional_methods.push( + quote! { + builder.method("abs", + |_, this, (): ()| { + Ok(#wrapper_ident(this.abs())) + }); + } + ) + } + + let optional_methods_iter = optional_methods.iter(); + quote! { + + + builder.method("clamp", + |_, this, (min, max): (#wrapper_ident, #wrapper_ident)| { + Ok(#wrapper_ident(this.clamp(min.0, max.0))) + }); + + // TODO: Not all Vecs have this + /* builder.method("clamp_length", + |_, this, (min, max): (f32, f32)| { + Ok(#wrapper_ident(this.clamp_length(min, max))) + }); */ + + + builder.method("to_array", + |_, this, (): ()| { + Ok(this.to_array()) + }); + + #(#optional_methods_iter)* + } + } +} + +pub(crate) struct WrapUsage { + pub type_path: Path, + /// The extra derives of the type. + pub derive_idents: Punctuated, + /// The field idents of the type that will be exposed with gets and sets + pub field_idents: Punctuated, + pub skip_new_fn: bool, + /// The identifiers that are taken as parameters in the types 'new' function + pub new_fn_idents: Punctuated, + pub meta_method_idents: Punctuated, + + pub matrix: Option, + pub vec: Option, + + pub custom_methods: Option, + pub custom_fields: Option, +} + +impl syn::parse::Parse for WrapUsage { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let type_path: Path = input.parse()?; + let mut s = Self { + type_path, + derive_idents: Punctuated::default(), + field_idents: Punctuated::default(), + skip_new_fn: false, + new_fn_idents: Punctuated::default(), + meta_method_idents: Punctuated::default(), + matrix: None, + vec: None, + custom_methods: None, + custom_fields: None, + }; + /* let mut derive_idents = None; + let mut field_idents = None; + let mut new_fn_idents = None; */ + + while input.peek(Token![,]) { + let _: Token![,] = input.parse()?; + //println!("Peeked a , ({:?})", input); + + if input.peek(syn::Ident) { + let ident: Ident = input.parse()?; + let ident_str = ident.to_string(); + let ident_str = ident_str.as_str(); + + match ident_str { + "derives" => { + if input.peek(token::Paren) { + let content; + let _parens: token::Paren = parenthesized!(content in input); + + let derives: Punctuated = content.parse_terminated(Ident::parse, Token![,])?; + s.derive_idents = derives; + //println!("read derives: {:?}", s.derive_idents); + } + }, + "fields" => { + if input.peek(token::Paren) { + let content; + let _parens: token::Paren = parenthesized!(content in input); + + let fields: Punctuated = content.parse_terminated(Ident::parse, Token![,])?; + s.field_idents = fields; + //println!("read fields: {:?}", s.field_idents); + } + }, + "new" => { + if input.peek(token::Paren) { + let content; + let _parens: token::Paren = parenthesized!(content in input); + + let fields: Punctuated = content.parse_terminated(Ident::parse, Token![,])?; + s.new_fn_idents = fields; + //println!("read fields: {:?}", s.new_fn_idents); + } + }, + "no_new" => { + s.skip_new_fn = true; + }, + "matrix" => { + if input.peek(token::Brace) { + let content; + let _braces = braced!(content in input); + s.matrix = Some(content.parse()?); + } + }, + "metamethods" => { + if input.peek(token::Paren) { + let content; + let _bracket: token::Paren = parenthesized!(content in input); + + let meta_methods: Punctuated = content.parse_terminated(MetaMethod::parse, Token![,])?; + s.meta_method_idents = meta_methods; + } + }, + "custom_methods" => { + let methods_block = input.parse()?; + s.custom_methods = Some(methods_block); + }, + "custom_fields" => { + let block = input.parse()?; + s.custom_fields = Some(block); + } + _ => { + return Err(syn::Error::new_spanned(ident, "unknown wrapper command")); + } + } + } + } + + Ok(s) + } +} + +/// Creates a wrapper type for a VecN from the engine math library. +#[proc_macro] +pub fn wrap_math_vec_copy(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as WrapUsage); + + let path: Path = input.type_path; + let type_name = &path.segments.last() + .expect("Failure to find typename in macro usage!") + .ident; + let wrapper_typename = Ident::new(&format!("Lua{}", type_name), Span::call_site()); + + let vec_wrapper = { + let name_str = type_name.to_string(); + if name_str.contains("Vec") { + Some(VecWrapper {}) + } else { + None + } + }; + // TODO: fix this so it doesn't cause a stack overflow + /* let vec_wrapper_fields = vec_wrapper.as_ref().map(|vec| + vec.to_field_tokens(&path, &wrapper_typename)); */ + let vec_wrapper_fields: Option = None; + let vec_wrapper_methods = vec_wrapper.as_ref().map(|vec| + vec.to_method_tokens(&path, &wrapper_typename)); + + let derive_idents_iter = input.derive_idents.iter(); + let custom_methods = input.custom_methods; + let custom_fields = input.custom_fields; + + let field_get_set_pairs = input.field_idents.iter().map(|i| { + let is = i.to_string(); + quote! { + builder.field_getter(#is, |_, this| { + Ok(this.#i) + }); + builder.field_setter(#is, |_, this, #i| { + this.#i = #i; + Ok(()) + }); + } + }); + + let new_fn_idents = { + let idents = if input.new_fn_idents.is_empty() { + input.field_idents.iter() + } else { + input.new_fn_idents.iter() + }; + + let idents_c = idents.clone(); + + if !input.skip_new_fn { + quote! { + builder.function("new", |_, ( #(#idents_c),* )| { + Ok(#wrapper_typename(#path::new( #(#idents),* ))) + }); + } + } else { quote!() } + }; + + let matrix_wrapper_methods = input.matrix.as_ref().map(|m| + m.to_method_tokens(&path, &wrapper_typename)); + let matrix_wrapper_fields = input.matrix.as_ref().map(|m| + m.to_field_tokens(&path, &wrapper_typename)); + + let meta_method_idents = { + let idents = input.meta_method_idents.iter().map(|metamethod| { + metamethod.to_tokens(&wrapper_typename) + }); + + quote! { + #(#idents)* + } + }; + + proc_macro::TokenStream::from(quote! { + #[derive(Clone, Copy, lyra_reflect::Reflect, #(#derive_idents_iter),*)] + pub struct #wrapper_typename(#[reflect(skip)] pub(crate) #path); + + impl std::ops::Deref for #wrapper_typename { + type Target = #path; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl std::ops::DerefMut for #wrapper_typename { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + impl<'lua> elua::FromLua<'lua> for #wrapper_typename { + fn from_lua(_lua: &'lua elua::State, value: elua::Value<'lua>) -> elua::Result { + match value { + elua::Value::Userdata(ud) => Ok(*ud.as_ref::()?), + _ => panic!("Attempt to get {} from a {} value", stringify!(#wrapper_typename), value.type_name()), + } + } + } + + impl elua::Userdata for #wrapper_typename { + fn name() -> String { + stringify!(#type_name).to_string() + } + + fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) { + #(#field_get_set_pairs)* + + #matrix_wrapper_fields + #vec_wrapper_fields + + #custom_fields + + #new_fn_idents + + builder.method(#FN_NAME_INTERNAL_REFLECT, |_, this, ()| { + Ok(crate::ScriptBorrow::from_component::<#path>(Some(this.0.clone()))) + }); + + builder.function(#FN_NAME_INTERNAL_REFLECT_TYPE, |_, ()| { + Ok(crate::ScriptBorrow::from_component::<#path>(None)) + }); + + #meta_method_idents + + #matrix_wrapper_methods + #vec_wrapper_methods + + #custom_methods + } + } + + impl lyra_scripting::lua::LuaWrapper for #wrapper_typename { + fn wrapped_type_id() -> std::any::TypeId { + let t = std::any::TypeId::of::<#path>(); + println!("Got id of {}, it is {:?}", stringify!(#path), t); + t + } + } + + impl<'a> elua::FromLuaVec<'a> for #wrapper_typename { + fn from_lua_value_vec(state: &'a elua::State, mut values: elua::ValueVec<'a>) -> elua::Result { + use elua::FromLua; + + if let Some(val) = values.pop_front() { + #wrapper_typename::from_lua(state, val) + } else { + Err(elua::Error::IncorrectArgCount { + arg_expected: 1, + arg_count: values.len() as i32, + }) + } + } + } + }) +} \ No newline at end of file diff --git a/lyra-scripting/lyra-scripting-derive/src/mat_wrapper.rs b/lyra-scripting/lyra-scripting-derive/src/mat_wrapper.rs new file mode 100644 index 0000000..770451f --- /dev/null +++ b/lyra-scripting/lyra-scripting-derive/src/mat_wrapper.rs @@ -0,0 +1,142 @@ +use proc_macro2::Ident; +use quote::quote; +use syn::{Path, Token}; + +pub(crate) struct MatWrapper { + pub column_type: Ident, +} + +impl MatWrapper { + pub fn to_field_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream { + quote! { + builder.field("ZERO", #wrapper_ident(#wrapped_path::ZERO)); + builder.field("IDENTITY", #wrapper_ident(#wrapped_path::IDENTITY)); + builder.field("NAN", #wrapper_ident(#wrapped_path::NAN)); + } + } + + pub fn to_method_tokens(&self, wrapped_path: &Path, wrapper_ident: &Ident) -> proc_macro2::TokenStream { + let column_type = &self.column_type; + + let column_size = { + let ty_str = column_type.to_string(); + ty_str[ty_str.len() - 1..].parse::() + .expect("Failure to parse number from token type") + }; + let column_size_xtwo = column_size * 2; + + let element_ty = quote!(f32); + + quote! { + builder.function("from_cols", + |_, (x_axis, y_axis): (#column_type, #column_type)| { + Ok(#wrapper_ident(#wrapped_path::from_cols(x_axis.0, y_axis.0))) + }); + + builder.function("from_cols_array", + |_, (arr,): ([#element_ty; #column_size_xtwo],)| { + Ok(#wrapper_ident(#wrapped_path::from_cols_array(&arr))) + }); + + builder.function("from_cols_array_2d", + |_, (arr,): ([[#element_ty; #column_size]; #column_size],)| { + Ok(#wrapper_ident(#wrapped_path::from_cols_array_2d(&arr))) + }); + + builder.function("from_diagonal", + |_, (diag,): (#column_type,)| { + Ok(#wrapper_ident(#wrapped_path::from_diagonal(diag.0))) + }); + + builder.method("col", + |_, this, (idx,): (usize,)| { + Ok(#column_type(this.col(idx))) + }); + + builder.method("row", + |_, this, (idx,): (usize,)| { + Ok(#column_type(this.row(idx))) + }); + + builder.method_mut("set_col", + |_, this, (idx, newc): (usize, #column_type)| { + let col = this.col_mut(idx); + *col = newc.0; + + Ok(()) + }); + + builder.method("is_finite", + |_, this, (): ()| { + Ok(this.is_finite()) + }); + + builder.method("is_nan", + |_, this, (): ()| { + Ok(this.is_nan()) + }); + + builder.method("transpose", + |_, this, (): ()| { + Ok(#wrapper_ident(this.0.transpose())) + }); + + builder.method("determinant", + |_, this, (): ()| { + Ok(this.determinant()) + }); + + builder.method("inverse", + |_, this, (): ()| { + Ok(#wrapper_ident(this.inverse())) + }); + + builder.method("abs_diff_eq", + |_, this, (rhs, max_abs_diff): (#wrapper_ident, f32)| { + Ok(this.abs_diff_eq(rhs.0, max_abs_diff)) + }); + + // TODO: After all DMat's are implemented + /* builder.method("as_dmat", + |_, this, (rhs, max_abs_diff): (#wrapper_ident, f32)| { + Ok(D#wrapper_ident(this.as_dmat)) + }); */ + } + } +} + +impl syn::parse::Parse for MatWrapper { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let mut column_type = None; + + // cba to remove the use of this bool + let mut first = true; + + while input.peek(Token![,]) || first { + if !first { + let _: Token![,] = input.parse()?; + } + + if input.peek(syn::Ident) { + let ident: Ident = input.parse()?; + let ident_str = ident.to_string(); + let ident_str = ident_str.as_str(); + + match ident_str { + "col_type" => { + let _eq: Token![=] = input.parse()?; + column_type = Some(input.parse()?); + }, + _ => return Err(syn::Error::new_spanned(ident, "unknown matrix wrapper command")), + } + } + + first = false; + } + + Ok(Self { + column_type: column_type.ok_or_else(|| syn::Error::new(input.span(), + "expected `col_type`"))?, + }) + } +} \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/init.lua b/lyra-scripting/scripts/lua/init.lua new file mode 100644 index 0000000..e69de29 diff --git a/lyra-scripting/scripts/lua/math/quat.lua b/lyra-scripting/scripts/lua/math/quat.lua new file mode 100644 index 0000000..b268834 --- /dev/null +++ b/lyra-scripting/scripts/lua/math/quat.lua @@ -0,0 +1,182 @@ +---@class Quat +---@field x number +---@field y number +---@field z number +---@field w number +Quat = { x = 0.0, y = 0.0, z = 0.0, w = 0.0 } +Quat.__index = Quat +Quat.__name = "Quat" + +--- Constructs a new Quaternion from x, y, z, and w. +---@param x number +---@param y number +---@param z number +---@param w number +---@return Quat +function Quat:new(x, y, z, w) + local q = {} + setmetatable(q, Quat) + + q.x = x + q.y = y + q.z = z + q.w = w + + return q +end + +Quat.IDENTITY = Quat:new(0, 0, 0, 1) + +function Quat:clone() + return Quat:new(self.x, self.y, self.z, self.w) +end + +--- Creates a quaternion from the angle, in radians, around the x axis. +--- @param rad number +--- @return Quat +function Quat:from_rotation_x(rad) + local sin = math.sin(rad * 0.5) + local cos = math.cos(rad * 0.5) + return Quat:new(sin, 0, 0, cos) +end + +--- Creates a quaternion from the angle, in radians, around the y axis. +--- @param rad number +--- @return Quat +function Quat:from_rotation_y(rad) + local sin = math.sin(rad * 0.5) + local cos = math.cos(rad * 0.5) + return Quat:new(0, sin, 0, cos) +end + +--- Creates a quaternion from the angle, in radians, around the z axis. +--- @param rad number +--- @return Quat +function Quat:from_rotation_z(rad) + local sin = math.sin(rad * 0.5) + local cos = math.cos(rad * 0.5) + return Quat:new(0, 0, sin, cos) +end + +--- Computes the dot product of `self`. +---@param rhs Quat +---@return number +function Quat:dot(rhs) + return (self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z) + (self.w * rhs.w) +end + +--- Computes the length of `self`. +---@return number +function Quat:length() + return math.sqrt(self:dot(self)) +end + +--- Compute the length of `self` squared. +---@return number +function Quat:length_squared() + return self:length() ^ 2 +end + +--- Normalizes `self` and returns the new Quat +---@return unknown +function Quat:normalize() + local length = self:length() + return self / length +end + +--- Multiplies two Quaternions together. Keep in mind that Quaternion multiplication is NOT +--- commutative so the order in which you multiply the quaternions matters. +---@param rhs Quat +---@return Quat +function Quat:mult_quat(rhs) + local x1, y1, z1, w1 = self.x, self.y, self.z, self.w + local x2, y2, z2, w2 = rhs.x, rhs.y, rhs.z, rhs.w + + local x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2 + local y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2 + local z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2 + local w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * x2 + + return Quat:new(x, y, z, w) +end + +--- Multiplies `self` by a Vec3, returning the rotated Vec3 +---@param vec Vec3 +---@return Vec3 +function Quat:mult_vec3(vec) + local vec_quat = Quat:new(vec.x, vec.y, vec.z, 0) + local quat = self:mult_quat(vec_quat) + return Vec3:new(quat.x, quat.y, quat.z) +end + +--- Calculates the linear iterpolation between `self` and `rhs` based on the `alpha`. +--- When `alpha` is `0`, the result will be equal to `self`. When `s` is `1`, the result +--- will be equal to `rhs` +--- @param rhs Quat +--- @param alpha number +--- @return Quat +function Quat:lerp(rhs, alpha) + -- ensure alpha is [0, 1] + local alpha = math.max(0, math.min(1, alpha)) + + local x1, y1, z1, w1 = self.x, self.y, self.z, self.w + local x2, y2, z2, w2 = rhs.x, rhs.y, rhs.z, rhs.w + + local x = (1 - alpha) * x1 + alpha * x2 + local y = (1 - alpha) * y1 + alpha * y2 + local z = (1 - alpha) * z1 + alpha * z2 + local w = (1 - alpha) * w1 + alpha * w2 + + return Quat:new(x, y, z, w):normalize() +end + +function Quat:__add(rhs) + return Quat:new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z, self.w + rhs.w) +end + +function Quat:__sub(rhs) + return Quat:new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z, self.w - rhs.w) +end + +function Quat:__mul(rhs) + if type(rhs) == "number" then + return Quat:new(self.x * rhs, self.y * rhs, self.z * rhs, self.w * rhs) + elseif type(rhs) == "table" then + local name = rhs.__name + + if name == "Vec3" then + return self:mult_vec3(rhs) + elseif name == "Quat" then + return self:mult_quat(rhs) + else + assert(false, "Unknown usertype of rhs" .. name) + end + else + assert(false, "Unknown type of rhs" .. type(rhs)) + end +end + +function Quat:__div(rhs) + if type(rhs) == "number" then + return Quat:new(self.x / rhs, self.y / rhs, self.z / rhs, self.w / rhs) + else + assert(rhs.__name == "Quat", "Attempted to divide Quat by unknown type " .. rhs.__name) + return Quat:new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z, self.w / rhs.w) + end +end + +function Quat:__eq(rhs) + return self.x == rhs.x and self.y == rhs.y and self.z == rhs.z and self.w == rhs.w +end + +function Quat:__lt(rhs) + return self.x < rhs.x and self.y < rhs.y and self.z < rhs.z and self.w < rhs.w +end + +function Quat:__le(rhs) + return self.x <= rhs.x and self.y <= rhs.y and self.z <= rhs.z and self.w <= rhs.w +end + +function Quat:__tostring() + return "Quat(" .. self.x .. ", " .. self.y .. ", " .. self.z .. ", " .. self.w .. ")" +end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/math/transform.lua b/lyra-scripting/scripts/lua/math/transform.lua new file mode 100644 index 0000000..7f3eb14 --- /dev/null +++ b/lyra-scripting/scripts/lua/math/transform.lua @@ -0,0 +1,95 @@ +---@class Transform +---@field translation Vec3 +---@field rotation Quat +---@field Scale Vec3 +Transform = { translation = Vec3.ZERO, rotation = Quat.IDENTITY, scale = Vec3.ONE } +Transform.__index = Transform +Transform.__name = "Transform" + +function Transform:new(translation, rotation, scale) + local t = {} + setmetatable(t, Transform) + + t.translation = translation + t.rotation = rotation + t.scale = scale + + return t +end + +function Transform:clone() + return Transform:new(self.translation:clone(), self.rotation:clone(), self.scale:clone()) +end + +--- Creates a new Transform with the translation at the vec3 +--- @param pos Vec3 +function Transform:from_vec3(pos) + local t = Transform:clone() -- copy of default transform + t.translation = pos + return t +end + +function Transform:from_xyz(x, y, z) + Transform:from_vec3(Vec3:new(x, y, z)) +end + +--- Calculates the forward vector of the Transform. +--- @return Vec3 +function Transform:forward() + return (self.rotation * Vec3.NEG_Z):normalize() +end + +--- Calculates the left vector of the Transform. +--- @return Vec3 +function Transform:left() + return (self.rotation * Vec3.X):normalize() +end + +--- Calculates the up vector of the Transform. +--- @return Vec3 +function Transform:up() + return (self.rotation * Vec3.Y):normalize() +end + +--- Rotates `self` using a Quaternion +--- @param quat Quat +function Transform:rotate(quat) + self.rotation = (quat * self.rotation):normalize() +end + +--- Rotates `self` around the x-axis +--- @param rad number +function Transform:rotate_x(rad) + self:rotate(Quat:from_rotation_x(rad)) +end + +--- Rotates `self` around the y-axis +--- @param rad number +function Transform:rotate_y(rad) + self:rotate(Quat:from_rotation_y(rad)) +end + +--- Rotates `self` around the z-axis +--- @param rad number +function Transform:rotate_z(rad) + self:rotate(Quat:from_rotation_z(rad)) +end + +--- Calculates the linear iterpolation between `self` and `rhs` based on the `alpha`. +--- When `alpha` is `0`, the result will be equal to `self`. When `s` is `1`, the result +--- will be equal to `rhs` +--- @param rhs Transform +--- @param alpha number +--- @return Transform +function Transform:lerp(rhs, alpha) + local res = self:clone() + res.translation = self.translation:lerp(rhs.translation, alpha) + res.rotation = self.rotation:lerp(rhs.rotation, alpha) + res.scale = self.scale:lerp(rhs.scale, alpha) + return res +end + +function Transform:__tostring() + return "Transform(pos=" .. tostring(self.translation) .. ", rot=" + .. tostring(self.rotation) .. ", scale=" .. tostring(self.scale) .. ")" +end \ No newline at end of file diff --git a/lyra-scripting/scripts/lua/math/vec3.lua b/lyra-scripting/scripts/lua/math/vec3.lua new file mode 100644 index 0000000..5b5ff44 --- /dev/null +++ b/lyra-scripting/scripts/lua/math/vec3.lua @@ -0,0 +1,187 @@ +---@class Vec3 +---@field x number +---@field y number +---@field z number +Vec3 = { x = 0.0, y = 0.0, z = 0.0 } +Vec3.__index = Vec3 +Vec3.__name = "Vec3" + +--- Constructs a new vector +---@param x number +---@param y number +---@param z number +---@return Vec3 +function Vec3:new(x, y, z) + local v = {} + setmetatable(v, Vec3) + + v.x = x + v.y = y + v.z = z + + return v +end + +---Creates a copy of self +---@return Vec3 +function Vec3:clone() + return Vec3:new(self.x, self.y, self.z) +end + +--- Constructs a vector with all elements as parameter `x`. +---@param x number +---@return Vec3 +function Vec3:all(x) + return Vec3:new(x, x, x) +end + +--- A unit-length vector pointing alongside the positive X axis. +Vec3.X = Vec3:new(1, 0, 0) +--- A unit-length vector pointing alongside the positive Y axis. +Vec3.Y = Vec3:new(0, 1, 0) +--- A unit-length vector pointing alongside the positive Z axis. +Vec3.Z = Vec3:new(0, 0, 1) + +--- A unit-length vector pointing alongside the negative X axis. +Vec3.NEG_X = Vec3:new(-1, 0, 0) +--- A unit-length vector pointing alongside the negative Y axis. +Vec3.NEG_Y = Vec3:new(0, -1, 0) +--- A unit-length vector pointing alongside the negative Z axis. +Vec3.NEG_Z = Vec3:new(0, 0, -1) + +--- A vector of all zeros +Vec3.ZERO = Vec3:new(0, 0, 0) +--- A vector of all ones +Vec3.ONE = Vec3:new(1, 1, 1) + +--- Computes the absolute value of `self`. +function Vec3:abs() + self.x = math.abs(self.x) + self.y = math.abs(self.y) + self.z = math.abs(self.z) +end + +--- Computes the length of `self`. +---@return number +function Vec3:length() + return math.sqrt(self:dot(self)) +end + +---Moves `self` by the provided coordinates +---@param x number +---@param y number +---@param z number +function Vec3:move_by(x, y, z) + self.x = self.x + x + self.y = self.y + y + self.z = self.z + z +end + +--- Computes the dot product of `self` and `rhs`. +---@param rhs Vec3 +---@return number +function Vec3:dot(rhs) + assert(rhs.__name == "Vec3") + + return (self.x * rhs.x) + (self.y * rhs.y) + (self.z * rhs.z) +end + +--- Returns a vector that has the minimum value of each element of `self` and `rhs` +---@param rhs Vec3 +---@return Vec3 +function Vec3:min(rhs) + local x = math.min(self.x, rhs.x) + local y = math.min(self.y, rhs.y) + local z = math.min(self.z, rhs.z) + + return Vec3:new(x, y, z) +end + +--- Modifies `self` to be normalized to a length 1. +function Vec3:normalize() + local len_recip = 1.0 / self:length() + self.x = self.x * len_recip + self.y = self.y * len_recip + self.z = self.z * len_recip +end + +--- Calculates the linear iterpolation between `self` and `rhs` based on the `alpha`. +--- When `alpha` is `0`, the result will be equal to `self`. When `s` is `1`, the result +--- will be equal to `rhs` +--- @param rhs Vec3 +--- @param alpha number +--- @return Vec3 +function Vec3:lerp(rhs, alpha) + -- ensure alpha is [0, 1] + local alpha = math.max(0, math.min(1, alpha)) + + local res = self:clone() + res = res + ((rhs - res) * alpha) + return res +end + +function Vec3:__add(rhs) + if type(rhs) == "Vec3" then + return Vec3:new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) + else + return Vec3:new(self.x + rhs, self.y + rhs, self.z + rhs) + end +end + +function Vec3:__sub(rhs) + if type(rhs) == "Vec3" then + return Vec3:new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) + else + return Vec3:new(self.x - rhs, self.y - rhs, self.z - rhs) + end +end + +function Vec3:__mul(rhs) + if type(rhs) == "Vec3" then + return Vec3:new(self.x * rhs.x, self.y * rhs.y, self.z * rhs.z) + else + return Vec3:new(self.x * rhs, self.y * rhs, self.z * rhs) + end +end + +function Vec3:__div(rhs) + if type(rhs) == "Vec3" then + return Vec3:new(self.x / rhs.x, self.y / rhs.y, self.z / rhs.z) + else + return Vec3:new(self.x / rhs, self.y / rhs, self.z / rhs) + end +end + +function Vec3:__idiv(rhs) + if type(rhs) == "Vec3" then + return Vec3:new(self.x // rhs.x, self.y // rhs.y, self.z // rhs.z) + else + return Vec3:new(self.x // rhs, self.y // rhs, self.z // rhs) + end +end + +function Vec3:__unm() + return Vec3:new(-self.x, -self.y, -self.z) +end + +function Vec3:__pow(rhs) + if type(rhs) == "number" then + return Vec3:new(self.x ^ rhs, self.y ^ rhs, self.z ^ rhs) + end +end + +function Vec3:__eq(rhs) + return self.x == rhs.x and self.y == rhs.y and self.z == rhs.z +end + +function Vec3:__lt(rhs) + return self.x < rhs.x and self.y < rhs.y and self.z < rhs.z +end + +function Vec3:__le(rhs) + return self.x <= rhs.x and self.y <= rhs.y and self.z <= rhs.z +end + +function Vec3:__tostring() + return "Vec3(" .. self.x .. ", " .. self.y .. ", " .. self.z .. ")" +end \ No newline at end of file diff --git a/lyra-scripting/src/host.rs b/lyra-scripting/src/host.rs index f422725..d51056b 100644 --- a/lyra-scripting/src/host.rs +++ b/lyra-scripting/src/host.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use lyra_ecs::ResourceObject; +use lyra_ecs::{ResourceObject, Entity, World}; use crate::ScriptWorldPtr; @@ -8,15 +8,15 @@ use crate::ScriptWorldPtr; pub enum ScriptError { #[error("{0}")] #[cfg(feature = "lua")] - MluaError(mlua::Error), + MluaError(elua::Error), #[error("{0}")] Other(anyhow::Error), } #[cfg(feature = "lua")] -impl From for ScriptError { - fn from(value: mlua::Error) -> Self { +impl From for ScriptError { + fn from(value: elua::Error) -> Self { ScriptError::MluaError(value) } } @@ -29,8 +29,12 @@ impl From for ScriptError { #[derive(Clone, Hash, PartialEq, Eq)] pub struct ScriptData { + /// The script id pub script_id: u64, + /// The name of the script pub name: String, + /// The entity that this script exists on + pub entity: Entity, } /// Provides an API to a scripting context. @@ -38,12 +42,15 @@ pub trait ScriptApiProvider { /// The type used as the script's context. type ScriptContext; - /// Exposes an API in the provided script context. - fn expose_api(&mut self, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>; + /// Prepare the ECS world for this api. Things like registering types with the type registry happen here. + fn prepare_world(&mut self, world: &mut World) { + let _ = world; // remove compiler warning + } - /// Create a script in the script host. - /// - /// This only creates the script for the host, it does not setup the script for execution. See [`ScriptHostProvider::setup_script`]. + /// Exposes an API in the provided script context. + fn expose_api(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>; + + /// Setup a script right before its 'init' method is called. fn setup_script(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>; /// A function that is used to update a script's environment. @@ -74,13 +81,18 @@ pub trait ScriptHost: Default + ResourceObject { /// Loads a script and creates a context for it. /// /// Before the script is executed, the API providers are exposed to the script. - fn load_script(&mut self, script: &[u8], script_data: &ScriptData, providers: &mut crate::ScriptApiProviders) -> Result; + fn load_script(&mut self, script: &[u8], script_data: &ScriptData, + providers: &mut crate::ScriptApiProviders) + -> Result; /// Setup a script for execution. - fn setup_script(&mut self, script_data: &ScriptData, ctx: &mut Self::ScriptContext, providers: &mut ScriptApiProviders) -> Result<(), ScriptError>; + fn setup_script(&mut self, script_data: &ScriptData, ctx: &mut Self::ScriptContext, + providers: &mut ScriptApiProviders) -> Result<(), ScriptError>; - /// Executes the update step for the script. - fn update_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData, ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders) -> Result<(), ScriptError>; + /// Calls a event function in the script. + fn call_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData, + ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders, + event_fn_name: &str) -> Result<(), ScriptError>; } #[derive(Default)] @@ -110,4 +122,12 @@ impl ScriptContexts { pub fn has_context(&self, script_id: u64) -> bool { self.contexts.contains_key(&script_id) } + + pub fn remove_context(&mut self, script_id: u64) -> Option { + self.contexts.remove(&script_id) + } + + pub fn len(&self) -> usize { + self.contexts.len() + } } \ No newline at end of file diff --git a/lyra-scripting/src/lib.rs b/lyra-scripting/src/lib.rs index f08e7b4..6d6a77a 100644 --- a/lyra-scripting/src/lib.rs +++ b/lyra-scripting/src/lib.rs @@ -2,6 +2,9 @@ pub mod lua; pub mod world; +use std::any::TypeId; + +use lyra_ecs::{Component, ResourceObject}; pub use world::*; pub mod wrap; @@ -15,16 +18,19 @@ pub use script::*; use lyra_game::game::Game; +// required for some proc macros :( #[allow(unused_imports)] pub(crate) mod lyra_engine { pub use lyra_ecs as ecs; + pub use lyra_reflect as reflect; } -use lyra_reflect::{ReflectedComponent, Reflect}; +use lyra_reflect::{ReflectedComponent, Reflect, FromType, ReflectedResource}; #[derive(Clone)] pub enum ReflectBranch { Component(ReflectedComponent), + Resource(ReflectedResource), } impl ReflectBranch { @@ -35,18 +41,51 @@ impl ReflectBranch { pub fn as_component_unchecked(&self) -> &ReflectedComponent { match self { ReflectBranch::Component(c) => c, - //_ => panic!("`self` is not an instance of `ReflectBranch::Component`") + _ => panic!("`self` is not an instance of `ReflectBranch::Component`") } } + /// Returns a boolean indicating if `self` is a reflection of a Component. pub fn is_component(&self) -> bool { matches!(self, ReflectBranch::Component(_)) } + + /// Gets self as a [`ReflectedResource`]. + /// + /// # Panics + /// If `self` is not a variant of [`ReflectBranch::Resource`]. + pub fn as_resource_unchecked(&self) -> &ReflectedResource { + match self { + ReflectBranch::Resource(v) => v, + _ => panic!("`self` is not an instance of `ReflectBranch::Component`") + } + } + + /// Gets self as a [`ReflectedResource`], returning `None` if self is not an instance of it. + pub fn as_resource(&self) -> Option<&ReflectedResource> { + match self { + ReflectBranch::Resource(v) => Some(v), + _ => None + } + } + + /// Returns a boolean indicating if `self` is a reflection of a Resource. + pub fn is_resource(&self) -> bool { + matches!(self, ReflectBranch::Resource(_)) + } + + /// Returns the type id of the reflected thing + pub fn reflect_type_id(&self) -> TypeId { + match self { + ReflectBranch::Component(c) => c.type_id, + ReflectBranch::Resource(r) => r.type_id, + } + } } pub struct ScriptBorrow { - reflect_branch: ReflectBranch, - data: Option>, + pub(crate) reflect_branch: ReflectBranch, + pub(crate) data: Option>, } impl Clone for ScriptBorrow { @@ -60,8 +99,37 @@ impl Clone for ScriptBorrow { } } +impl ScriptBorrow { + /// Creates a ScriptBorrow from a Component + pub fn from_component(data: Option) -> Self + where + T: Reflect + Component + 'static + { + let data = data.map(|d| Box::new(d) as Box<(dyn Reflect + 'static)>); + + Self { + reflect_branch: ReflectBranch::Component(>::from_type()), + data, + } + } + + /// Creates a ScriptBorrow from a Resource. + pub fn from_resource(data: Option) -> Self + where + T: Reflect + ResourceObject + 'static + { + let data = data.map(|d| Box::new(d) as Box<(dyn Reflect + 'static)>); + + Self { + reflect_branch: ReflectBranch::Resource(>::from_type()), + data, + } + } +} + /// An extension trait that adds some helpful methods that makes it easier to do scripting things pub trait GameScriptExt { + /// A helper method for adding a ScriptApiProvider into the world. fn add_script_api_provider(&mut self, provider: P) where T: ScriptHost, @@ -69,12 +137,13 @@ pub trait GameScriptExt { } impl GameScriptExt for Game { - fn add_script_api_provider(&mut self, provider: P) + fn add_script_api_provider(&mut self, mut provider: P) where T: ScriptHost, P: ScriptApiProvider + 'static { - let world = self.world(); + let world = self.world_mut(); + provider.prepare_world(world); let mut providers = world.get_resource_mut::>(); providers.add_provider(provider); } diff --git a/lyra-scripting/src/lua/dynamic_iter.rs b/lyra-scripting/src/lua/dynamic_iter.rs index 5b6b300..c504470 100644 --- a/lyra-scripting/src/lua/dynamic_iter.rs +++ b/lyra-scripting/src/lua/dynamic_iter.rs @@ -1,6 +1,6 @@ use std::{ptr::NonNull, ops::{Range, Deref}}; -use lyra_ecs::{ComponentColumn, ComponentInfo, Archetype, ArchetypeId, ArchetypeEntityId, query::dynamic::{DynamicType, QueryDynamicType}, query::Fetch}; +use lyra_ecs::{ComponentColumn, ComponentInfo, Archetype, ArchetypeId, ArchetypeEntityId, query::dynamic::{DynamicType, QueryDynamicType}, query::Fetch, Entity}; use lyra_reflect::TypeRegistry; #[cfg(feature = "lua")] @@ -45,6 +45,11 @@ impl<'a> From> for FetchDynamicTy } } +pub struct DynamicViewRow { + entity: Entity, + item: Vec, +} + #[derive(Clone)] pub struct DynamicViewIter { world_ptr: ScriptWorldPtr, @@ -69,15 +74,15 @@ impl<'a> From> for DynamicViewIter } impl Iterator for DynamicViewIter { - type Item = Vec; + type Item = DynamicViewRow; fn next(&mut self) -> Option { loop { if let Some(entity_index) = self.component_indices.next() { let mut fetch_res = vec![]; + let entity_index = ArchetypeEntityId(entity_index); for fetcher in self.fetchers.iter_mut() { - let entity_index = ArchetypeEntityId(entity_index); if !fetcher.can_visit_item(entity_index) { break; } else { @@ -90,7 +95,14 @@ impl Iterator for DynamicViewIter { continue; } - return Some(fetch_res); + let arch = unsafe { self.archetypes.get_unchecked(self.next_archetype - 1).as_ref() }; + let entity = arch.entity_of_index(entity_index).unwrap(); + let row = DynamicViewRow { + entity, + item: fetch_res, + }; + + return Some(row); } else { if self.next_archetype >= self.archetypes.len() { return None; // ran out of archetypes to go through @@ -108,7 +120,6 @@ impl Iterator for DynamicViewIter { continue; } - //let world = unsafe { self.world_ptr.as_ref() }; let world = self.world_ptr.as_ref(); self.fetchers = self.queries.iter() @@ -121,6 +132,19 @@ impl Iterator for DynamicViewIter { } } +#[cfg(feature = "lua")] +pub struct ReflectedItem<'a> { + //pub proxy: &'a ReflectLuaProxy, + pub comp_ptr: NonNull, + pub comp_val: elua::Value<'a>, +} + +#[cfg(feature = "lua")] +pub struct ReflectedRow<'a> { + pub entity: Entity, + pub row: Vec>, +} + pub struct ReflectedIterator { pub world: ScriptWorldPtr, pub dyn_view: DynamicViewIter, @@ -128,10 +152,9 @@ pub struct ReflectedIterator { } impl ReflectedIterator { - - #[cfg(feature = "lua")] - pub fn next_lua<'a>(&mut self, lua: &'a mlua::Lua) -> Option), mlua::AnyUserData<'a>)>> { + pub fn next_lua<'a>(&mut self, lua: &'a elua::State) -> Option> { + use elua::AsLua; let n = self.dyn_view.next(); @@ -142,23 +165,32 @@ impl ReflectedIterator { .map(|r| NonNull::from(r.deref())); } - let mut dynamic_row = Vec::new(); - for d in row.iter() { + let mut dynamic_row = vec![]; + for d in row.item.iter() { let id = d.info.type_id.as_rust(); let reflected_components = unsafe { self.reflected_components.as_ref().unwrap().as_ref() }; let reg_type = reflected_components.get_type(id) - .expect("Could not find type for dynamic view!"); + .expect("Requested type was not found in TypeRegistry"); let proxy = reg_type.get_data::() + // TODO: properly handle this error .expect("Type does not have ReflectLuaProxy as a TypeData"); - - let userdata = (proxy.fn_as_uservalue)(lua, d.ptr).unwrap(); + let value = (proxy.fn_as_lua)(lua, d.ptr.cast()).unwrap() + .as_lua(lua).unwrap(); - dynamic_row.push(( (proxy, d.ptr), userdata)); + dynamic_row.push(ReflectedItem { + comp_ptr: d.ptr, + comp_val: value + }); } - Some(dynamic_row) + let row = ReflectedRow { + entity: row.entity, + row: dynamic_row + }; + + Some(row) } else { None } diff --git a/lyra-scripting/src/lua/loader.rs b/lyra-scripting/src/lua/loader.rs index 95aee98..eea2efd 100644 --- a/lyra-scripting/src/lua/loader.rs +++ b/lyra-scripting/src/lua/loader.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use lyra_resource::{ResourceLoader, Resource}; +use lyra_resource::{ResourceLoader, ResHandle}; #[derive(Debug, Clone)] pub struct LuaScript { @@ -13,7 +13,7 @@ pub struct LuaLoader; impl ResourceLoader for LuaLoader { fn extensions(&self) -> &[&str] { - &[".lua"] + &["lua"] } fn mime_types(&self) -> &[&str] { @@ -23,7 +23,7 @@ impl ResourceLoader for LuaLoader { fn load(&self, _resource_manager: &mut lyra_resource::ResourceManager, path: &str) -> Result, lyra_resource::LoaderError> { let bytes = std::fs::read(path)?; - let s = Resource::with_data(path, LuaScript { + let s = ResHandle::with_data(path, LuaScript { bytes }); @@ -34,7 +34,7 @@ impl ResourceLoader for LuaLoader { let end = offset + length; let bytes = bytes[offset..end].to_vec(); - let s = Resource::with_data("from bytes", LuaScript { + let s = ResHandle::with_data("from bytes", LuaScript { bytes }); diff --git a/lyra-scripting/src/lua/mod.rs b/lyra-scripting/src/lua/mod.rs index 4cc9a7d..ce37ef5 100644 --- a/lyra-scripting/src/lua/mod.rs +++ b/lyra-scripting/src/lua/mod.rs @@ -2,8 +2,7 @@ pub mod dynamic_iter; pub use dynamic_iter::*; pub mod world; -use lyra_game::plugin::Plugin; -use lyra_resource::ResourceManager; +use elua::FromLua; pub use world::*; pub mod script; @@ -12,120 +11,174 @@ pub use script::*; pub mod loader; pub use loader::*; -#[cfg(test)] -mod test; +pub mod providers; +pub mod wrappers; -use std::{ptr::NonNull, sync::Mutex}; +pub mod proxy; +pub use proxy::*; -use lyra_ecs::DynamicBundle; -use lyra_reflect::{Reflect, RegisteredType, FromType, AsRegisteredType}; +pub mod system; +pub use system::*; -use mlua::{Lua, AnyUserDataExt}; +use std::{any::TypeId, sync::Mutex}; +use lyra_ecs::{ + Component, ComponentInfo, World +}; +use lyra_reflect::{Reflect, TypeRegistry}; +use crate::ScriptBorrow; + +pub type LuaContext = Mutex; + +/// Name of a Lua function that is used to Reflect the Userdata, but without a value. +/// +/// This is used for reflecting the userdata as an ECS Component or Resource. This **function** +/// returns a [`ScriptBorrow`] with data as `None`. pub const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type"; + +/// Name of a Lua function that is used to Reflect the Userdata. +/// +/// This is used for reflecting the userdata as an ECS Component or Resource. This **method** +/// returns a [`ScriptBorrow`] with data as `Some`. **Anything that calls this expects the +/// method to return data**. pub const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect"; -use crate::{ScriptBorrow, ScriptDynamicBundle, ScriptApiProviders, ScriptContexts}; +/// Name of a Lua function implemented for Userdata types that can be made into components. +/// +/// This is used for types that can be converted into components. When implementing this function, +/// you must return a [`ScriptBorrow`] that contains the component for this userdata. +/// You can return [`elua::Value::Nil`] if for some reason the type could not be converted +/// into a component. +/// +/// A good example of this is `LuaResHandle`. The resource handle is requested from the +/// world, and could be a 3d model. The 3d model could then be manually wrapped as +/// [`LuaModelComponent`] with its `new` function. But for quality of life, this internal +/// function was created so that the userdata can be converted into its component +/// type without having to wrap it. +/// +/// Without implementing this function: +/// ```lua +/// local cube = world:request_res("assets/cube-texture-embedded.gltf") +/// local cube_comp = ModelComponent.new(cube) -- annoying to write +/// +/// local pos = Transform.from_translation(Vec3.new(0, 0, -8.0)) +/// world:spawn(pos, cube_comp) +/// ``` +/// +/// With this function: +/// ```lua +/// local cube = world:request_res("assets/cube-texture-embedded.gltf") +/// local pos = Transform.from_translation(Vec3.new(0, 0, -8.0)) +/// world:spawn(pos, cube) +/// ``` +pub const FN_NAME_INTERNAL_AS_COMPONENT: &str = "__lyra_internal_refl_as_component"; -impl<'lua> mlua::FromLua<'lua> for ScriptBorrow { - fn from_lua(value: mlua::Value<'lua>, _lua: &'lua Lua) -> mlua::Result { +/// A trait used for registering a Lua type with the world. +pub trait RegisterLuaType { + /// Register a type to Lua that **is not wrapped**. + fn register_lua_type<'a, T>(&mut self) + where + T: Reflect + LuaProxy + Clone + elua::FromLua<'a> + elua::Userdata; + + /// Registers a type to Lua that is wrapped another type. + /// This would be used for something like `UserdataRef`. + fn register_lua_wrapper<'a, W>(&mut self) + where + W: Reflect + LuaProxy + LuaWrapper + Clone + elua::FromLua<'a> + elua::Userdata; + + /// Registers a type to Lua that can be converted into and from Lua types. + fn register_lua_convert(&mut self) + where + T: Clone + for<'a> elua::FromLua<'a> + for<'a> elua::AsLua<'a> + LuaWrapper + 'static; + + /// Registers a type to Lua that implements [`elua::TableProxy`] + fn register_lua_table_proxy<'a, T, W>(&mut self) + where + T: elua::TableProxy + 'static, + W: Component; +} + +impl RegisterLuaType for World { + fn register_lua_type<'a, T>(&mut self) + where + T: Reflect + LuaProxy + Clone + elua::FromLua<'a> + elua::Userdata + { + let mut registry = self.get_resource_mut::(); + + let type_id = TypeId::of::(); + + let reg_type = registry.get_type_or_default(type_id); + reg_type.add_data(ReflectLuaProxy::from_lua_proxy::()); + } + + fn register_lua_wrapper<'a, W>(&mut self) + where + W: Reflect + LuaProxy + LuaWrapper + Clone + elua::FromLua<'a> + elua::Userdata + { + let mut registry = self.get_resource_mut::(); + + let reg_type = registry.get_type_or_default(W::wrapped_type_id()); + reg_type.add_data(ReflectLuaProxy::from_lua_proxy::()); + } + + fn register_lua_convert(&mut self) + where + T: Clone + for<'a> elua::FromLua<'a> + for<'a> elua::AsLua<'a> + LuaWrapper + 'static, + { + let mut registry = self.get_resource_mut::(); + + let reg_type = registry.get_type_or_default(T::wrapped_type_id()); + reg_type.add_data(ReflectLuaProxy::from_as_and_from_lua::()); + } + + fn register_lua_table_proxy<'a, T, C>(&mut self) + where + T: elua::TableProxy + 'static, + C: Component + { + let mut registry = self.get_resource_mut::(); + + let reg_type = registry.get_type_or_default(TypeId::of::()); + reg_type.add_data(ReflectLuaProxy::from_table_proxy::()); + drop(registry); + + let mut lookup = self.get_resource_or_else::(LuaTableProxyLookup::default); + lookup.typeid_from_name.insert(T::table_name(), TypeId::of::()); + + let info = ComponentInfo::new::(); + lookup.comp_info_from_name.insert(T::table_name(), info); + } +} + +impl<'lua> elua::FromLua<'lua> for ScriptBorrow { + fn from_lua(_: &'lua elua::State, value: elua::Value<'lua>) -> elua::Result { match value { - mlua::Value::UserData(ud) => Ok(ud.borrow::()?.clone()), + elua::Value::Userdata(ud) => Ok(ud.as_ref::()?.clone()), _ => unreachable!(), } } } -impl mlua::UserData for ScriptBorrow {} - -pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow { - ud.call_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ()) - .expect("Type does not implement '__internal_reflect' properly") -} - -pub trait LuaProxy { - fn as_lua_value<'lua>(lua: &'lua mlua::Lua, this: &dyn Reflect) -> mlua::Result>; - fn apply(lua: &mlua::Lua, this: &mut dyn Reflect, apply: &mlua::AnyUserData) -> mlua::Result<()>; -} - -impl<'a, T: Reflect + Clone + mlua::FromLua<'a> + mlua::UserData> LuaProxy for T { - fn as_lua_value<'lua>(lua: &'lua mlua::Lua, this: &dyn Reflect) -> mlua::Result> { - let this = this.as_any().downcast_ref::().unwrap(); - lua.create_userdata(this.clone()) - } - - fn apply(_lua: &mlua::Lua, this: &mut dyn Reflect, apply: &mlua::AnyUserData) -> mlua::Result<()> { - let this = this.as_any_mut().downcast_mut::().unwrap(); - let apply = apply.borrow::()?; - - *this = apply.clone(); - - Ok(()) - } -} - -#[derive(Clone)] -pub struct ReflectLuaProxy { - fn_as_uservalue: for<'a> fn(lua: &'a Lua, this_ptr: NonNull) -> mlua::Result>, - fn_apply: for<'a> fn(lua: &'a Lua, this_ptr: NonNull, apply: &'a mlua::AnyUserData<'a>) -> mlua::Result<()>, -} - -impl<'a, T: Reflect + LuaProxy + Clone + mlua::FromLua<'a> + mlua::UserData> FromType for ReflectLuaProxy { - fn from_type() -> Self { - Self { - fn_as_uservalue: |lua, this| -> mlua::Result { - let this = unsafe { this.cast::().as_ref() }; - ::as_lua_value(lua, this) - }, - fn_apply: |lua, ptr, apply| { - let this = unsafe { ptr.cast::().as_mut() }; - ::apply(lua, this, apply) - } +impl<'lua> elua::FromLuaVec<'lua> for ScriptBorrow { + fn from_lua_value_vec(state: &'lua elua::State, mut values: elua::ValueVec<'lua>) -> elua::Result { + if let Some(v) = values.pop_front() { + ScriptBorrow::from_lua(state, v) + } else { + Err(elua::Error::Nil) } } } -impl<'lua> mlua::FromLua<'lua> for ScriptDynamicBundle { - fn from_lua(value: mlua::Value<'lua>, _lua: &'lua Lua) -> mlua::Result { - match value { - mlua::Value::UserData(ud) => Ok(ud.borrow::()?.clone()), - mlua::Value::Nil => Err(mlua::Error::FromLuaConversionError { from: "Nil", to: "DynamicBundle", message: Some("Value was nil".to_string()) }), - _ => panic!(), - } +impl elua::Userdata for ScriptBorrow { + fn name() -> String { + "ScriptBorrow".to_string() } + + fn build<'a>(_: &mut elua::UserdataBuilder<'a, Self>) { } } -impl mlua::UserData for ScriptDynamicBundle { - fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_function("new", |_, ()| { - Ok(ScriptDynamicBundle(DynamicBundle::new())) - }); - - methods.add_method_mut("push", |_, this, (comp,): (mlua::AnyUserData,)| { - let script_brw = comp.call_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?; - let reflect = script_brw.reflect_branch.as_component_unchecked(); - - let refl_data = script_brw.data.unwrap(); - let refl_data = refl_data.as_ref(); - reflect.bundle_insert(&mut this.0, refl_data); - - Ok(()) - }); - } -} - -#[derive(Default)] -pub struct LuaScriptingPlugin; - -impl Plugin for LuaScriptingPlugin { - fn setup(&self, game: &mut lyra_game::game::Game) { - let world = game.world(); - - world.add_resource_default::(); - world.add_resource_default::>(); - world.add_resource_default::>>(); - - let mut loader = world.get_resource_or_else(ResourceManager::default); - loader.register_loader::(); - } +/// Helper function used for reflecting userdata as a ScriptBorrow +pub fn reflect_user_data(ud: &elua::AnyUserdata) -> ScriptBorrow { + ud.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ()) + .expect("Type does not implement internal reflect method properly") } \ No newline at end of file diff --git a/lyra-scripting/src/lua/providers/ecs.rs b/lyra-scripting/src/lua/providers/ecs.rs new file mode 100644 index 0000000..7982271 --- /dev/null +++ b/lyra-scripting/src/lua/providers/ecs.rs @@ -0,0 +1,49 @@ +use lyra_ecs::ResourceObject; +use lyra_reflect::Reflect; + +use crate::{lua::{wrappers::{LuaActionHandler, LuaDeltaTime, LuaModelComponent}, LuaContext, RegisterLuaType, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr}; + +#[derive(Default)] +pub struct LyraEcsApiProvider; + +impl ScriptApiProvider for LyraEcsApiProvider { + type ScriptContext = LuaContext; + + fn prepare_world(&mut self, world: &mut lyra_ecs::World) { + world.register_lua_convert::(); + world.register_lua_wrapper::(); + world.register_lua_wrapper::(); + } + + fn expose_api(&mut self, _: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + let ctx = ctx.lock().unwrap(); + + let globals = ctx.globals()?; + globals.set("World", ctx.create_proxy::()?)?; + globals.set("DynamicBundle", ctx.create_proxy::()?)?; + globals.set("ModelComponent", ctx.create_proxy::()?)?; + globals.set("ActionHandler", ctx.create_proxy::()?)?; + + let dt_table = create_reflect_table::(&ctx)?; + globals.set("DeltaTime", dt_table)?; + + Ok(()) + } + + fn setup_script(&mut self, _: &crate::ScriptData, _: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + Ok(()) + } + + fn update_script_environment(&mut self, _: crate::ScriptWorldPtr, _: &crate::ScriptData, _: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + Ok(()) + } +} + +fn create_reflect_table(lua: &elua::State) -> elua::Result { + let table = lua.create_table()?; + table.set(FN_NAME_INTERNAL_REFLECT_TYPE, lua.create_function(|_, ()| { + Ok(ScriptBorrow::from_resource::(None)) + })?)?; + + Ok(table) +} \ No newline at end of file diff --git a/lyra-scripting/src/lua/providers/math.rs b/lyra-scripting/src/lua/providers/math.rs new file mode 100644 index 0000000..f0a33b7 --- /dev/null +++ b/lyra-scripting/src/lua/providers/math.rs @@ -0,0 +1,41 @@ +use lyra_ecs::World; +use crate::lua::wrappers::{LuaQuat, LuaTransform, LuaVec3}; +use crate::ScriptData; +use crate::lua::RegisterLuaType; + +use crate::{ScriptApiProvider, lua::LuaContext}; + +#[derive(Default)] +pub struct LyraMathApiProvider; + +impl ScriptApiProvider for LyraMathApiProvider { + type ScriptContext = LuaContext; + + fn prepare_world(&mut self, world: &mut World) { + world.register_lua_wrapper::(); + world.register_lua_wrapper::(); + world.register_lua_wrapper::(); + } + + fn expose_api(&mut self, _data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + let ctx = ctx.lock().unwrap(); + + /* let bytes = include_bytes!("../../../scripts/lua/math/transform.lua"); + ctx.load("lyra/math/transform.lua", bytes.as_slice())?.execute(())?; */ + + let globals = ctx.globals()?; + globals.set("Vec3", ctx.create_proxy::()?)?; + globals.set("Quat", ctx.create_proxy::()?)?; + globals.set("Transform", ctx.create_proxy::()?)?; + + Ok(()) + } + + fn setup_script(&mut self, _: &crate::ScriptData, _: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + Ok(()) + } + + fn update_script_environment(&mut self, _: crate::ScriptWorldPtr, _: &crate::ScriptData, _: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + Ok(()) + } +} \ No newline at end of file diff --git a/lyra-scripting/src/lua/providers/mod.rs b/lyra-scripting/src/lua/providers/mod.rs new file mode 100644 index 0000000..d8a3f49 --- /dev/null +++ b/lyra-scripting/src/lua/providers/mod.rs @@ -0,0 +1,8 @@ +pub mod util; +pub use util::*; + +pub mod math; +pub use math::*; + +pub mod ecs; +pub use ecs::*; \ No newline at end of file diff --git a/lyra-scripting/src/lua/providers/util.rs b/lyra-scripting/src/lua/providers/util.rs new file mode 100644 index 0000000..2ef6bc9 --- /dev/null +++ b/lyra-scripting/src/lua/providers/util.rs @@ -0,0 +1,134 @@ +use std::sync::{Mutex, Arc}; + +use tracing::{debug_span, debug}; + +use crate::{ScriptApiProvider, ScriptData}; + +/// This Api provider provides some nice utility functions. +/// +/// Functions: +/// ```lua +/// ---@param str (string) A format string. +/// ---@param ... (any varargs) The variables to format into the string. These values must be +/// primitives, or if UserData, have the '__tostring' meta method +/// function printf(str, ...) +/// ``` +#[derive(Default)] +pub struct UtilityApiProvider; + +impl ScriptApiProvider for UtilityApiProvider { + type ScriptContext = Mutex; + + fn expose_api(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + let ctx = ctx.lock().unwrap(); + + //fn printf(lua: &elua::State, (mut text, formats): (String, elua::Variadic)) -> elua::Result<()> { + let printf = |lua: &elua::State, (mut text, formats): (String, elua::Variadic)| { + let mut formatted = String::new(); + let mut arg_num = 0; + + while let Some(start) = text.find("{}") { + let val_str = match formats.get(arg_num) { + Some(v) => match v { + elua::Value::Nil => "nil".to_string(), + elua::Value::Boolean(b) => b.to_string(), + elua::Value::Number(n) => n.to_string(), + elua::Value::String(s) => s.clone(), + elua::Value::Table(_) => { + return Err(elua::Error::runtime("unable to get string representation of Table")); + }, + elua::Value::Function(_) => { + return Err(elua::Error::runtime("unable to get string representation of Function")); + }, + elua::Value::Thread(_) => { + return Err(elua::Error::runtime("unable to get string representation of Thread")); + }, + elua::Value::Userdata(ud) => { + if let Ok(tos) = ud.get::<_, elua::Function>(elua::MetaMethod::ToString) { + tos.exec::<_, String>(())? + } else { + return Err(elua::Error::runtime("UserData does not implement MetaMethod '__tostring'")); + } + }, + elua::Value::None => "None".to_string(), + elua::Value::Multi(_) => { + return Err(elua::Error::runtime("unable to get string representation of ValueVec")); + }, + }, + None => { + let got_args = arg_num;// - 1; + + // continue searching for {} to get the number of format spots for the error message. + while let Some(start) = text.find("{}") { + text = text[start + 2..].to_string(); + arg_num += 1; + } + + return Err(elua::Error::BadArgument { + func: Some("printf".to_string()), + arg_index: 2, + arg_name: Some("fmt...".to_string()), + error: Arc::new(elua::Error::Runtime(format!( + "not enough args \ + given for the amount of format areas in the string. Expected {}, \ + got {}.", arg_num, got_args + ))) + }) + }, + }; + + formatted = format!("{}{}{}", formatted, &text[0..start], val_str); + + text = text[start + 2..].to_string(); + + arg_num += 1; + } + + if arg_num < formats.len() { + return Err(elua::Error::BadArgument { + func: Some("printf".to_string()), + arg_index: 2, + arg_name: Some("fmt...".to_string()), + error: Arc::new(elua::Error::Runtime(format!( + "got more args \ + than format areas in the string. Expected {}, got {}.", formats.len(), arg_num + ))) + }) + } + + formatted = format!("{}{}", formatted, text); + + lua.globals()? + .get::<_, elua::Function>("print")? + .exec::<_, ()>(formatted)?; + + Ok(()) + }; + + let script_name_reg = ctx.registry_insert(data.name.clone())?; + + let printf_func = ctx.create_function(printf)?; + let print_func = ctx.create_function(move |lua, text: String| { + let name = lua.registry_get::(script_name_reg)?; + let _span = debug_span!("lua", script = &name).entered(); + + debug!(target: "lyra_scripting::lua", "{}", text); + + Ok(()) + })?; + + let globals = ctx.globals()?; + globals.set("printf", printf_func)?; + globals.set("print", print_func)?; + + Ok(()) + } + + fn setup_script(&mut self, _data: &ScriptData, _ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + Ok(()) + } + + fn update_script_environment(&mut self, _world: crate::ScriptWorldPtr, _data: &ScriptData, _ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { + Ok(()) + } +} \ No newline at end of file diff --git a/lyra-scripting/src/lua/proxy.rs b/lyra-scripting/src/lua/proxy.rs new file mode 100644 index 0000000..f43faf2 --- /dev/null +++ b/lyra-scripting/src/lua/proxy.rs @@ -0,0 +1,179 @@ +use std::{any::TypeId, collections::HashMap, ptr::NonNull}; + +use elua::{FromLua, TableProxy, AsLua}; +use lyra_ecs::{ComponentInfo, DynamicBundle}; +use lyra_reflect::Reflect; + +use crate::{ScriptBorrow, ScriptDynamicBundle}; + +use super::FN_NAME_INTERNAL_REFLECT; + +pub trait LuaWrapper { + /// The type id of the wrapped type. + fn wrapped_type_id() -> TypeId; +} + +/// A trait that used to convert something into lua, or to set something to a value from lua. +pub trait LuaProxy { + fn as_lua_value<'lua>( + lua: &'lua elua::State, + this: &dyn Reflect, + ) -> elua::Result>; + + fn apply( + lua: &elua::State, + this: &mut dyn Reflect, + value: &elua::Value, + ) -> elua::Result<()>; +} + +impl<'a, T> LuaProxy for T +where + T: Reflect + Clone + elua::FromLua<'a> + elua::Userdata +{ + fn as_lua_value<'lua>( + lua: &'lua elua::State, + this: &dyn Reflect, + ) -> elua::Result> { + let this = this.as_any().downcast_ref::().unwrap(); + lua.create_userdata(this.clone()) + .and_then(|ud| ud.as_lua(lua)) + } + + fn apply( + _: &elua::State, + this: &mut dyn Reflect, + apply: &elua::Value, + ) -> elua::Result<()> { + let this = this.as_any_mut().downcast_mut::().unwrap(); + let apply = apply.as_userdata() + .expect("Somehow a non-userdata Lua Value was provided to a LuaProxy") + .as_ref::()?; + + *this = apply.clone(); + + Ok(()) + } +} + +/// A struct that is used for retrieving rust type ids of types that implement `TableProxy`. +#[derive(Default)] +pub struct LuaTableProxyLookup { + pub(crate) typeid_from_name: HashMap, + pub(crate) comp_info_from_name: HashMap, +} + +/// A struct used for Proxying types to and from Lua. +#[derive(Clone)] +pub struct ReflectLuaProxy { + pub fn_as_lua: + for<'a> fn(lua: &'a elua::State, this_ptr: NonNull<()>) -> elua::Result>, + pub fn_apply: for<'a> fn( + lua: &'a elua::State, + this_ptr: NonNull<()>, + value: &'a elua::Value<'a>, + ) -> elua::Result<()>, +} + +impl ReflectLuaProxy { + /// Create from a type that implements LuaProxy (among some other required traits) + pub fn from_lua_proxy<'a, T>() -> Self + where + T: Reflect + LuaProxy + { + Self { + fn_as_lua: |lua, this| -> elua::Result { + let this = unsafe { this.cast::().as_ref() }; + ::as_lua_value(lua, this) + }, + fn_apply: |lua, ptr, apply| { + let this = unsafe { ptr.cast::().as_mut() }; + ::apply(lua, this, apply) + }, + } + } + + pub fn from_table_proxy() -> Self + where + T: TableProxy + { + Self { + fn_as_lua: |lua, this| -> elua::Result { + let this = unsafe { this.cast::().as_ref() }; + this.as_table(lua) + .and_then(|t| t.as_lua(lua)) + }, + fn_apply: |lua, ptr, value| { + let this = unsafe { ptr.cast::().as_mut() }; + let table = value.as_table() + .expect("Somehow a non-Table Lua Value was provided to a TableProxy"); + let new_val = T::from_table(lua, table.clone())?; + + *this = new_val; + + Ok(()) + }, + } + } + + /// Create from a type that implements FromLua and AsLua + pub fn from_as_and_from_lua() -> Self + where + T: for<'a> elua::FromLua<'a> + for<'a> elua::AsLua<'a> + Clone + { + Self { + fn_as_lua: |lua, this| -> elua::Result { + let this = unsafe { this.cast::().as_ref() }; + this.clone().as_lua(lua) + }, + fn_apply: |lua, ptr, value| { + let this = unsafe { ptr.cast::().as_mut() }; + let new_val = T::from_lua(lua, value.clone())?; + + *this = new_val; + + Ok(()) + }, + } + } +} + +impl<'lua> elua::FromLua<'lua> for ScriptDynamicBundle { + fn from_lua(_: &'lua elua::State, val: elua::Value<'lua>) -> elua::Result { + match val { + elua::Value::Userdata(ud) => Ok(ud.as_ref::()?.clone()), + elua::Value::Nil => Err(elua::Error::Nil), + _ => unreachable!(), + } + } +} + +impl<'lua> elua::FromLuaVec<'lua> for ScriptDynamicBundle { + fn from_lua_value_vec(state: &'lua elua::State, mut values: elua::ValueVec<'lua>) -> elua::Result { + if let Some(v) = values.pop_front() { + Ok(ScriptDynamicBundle::from_lua(state, v)?) + } else { + Err(elua::Error::Nil) + } + } +} + +impl elua::Userdata for ScriptDynamicBundle { + fn name() -> String { + "Bundle".to_string() + } + + fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) { + builder + .function("new", |_, ()| Ok(ScriptDynamicBundle(DynamicBundle::new()))) + .method_mut("push", |_, this, comp: elua::AnyUserdata| { + let script_brw = comp.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?; + let reflect = script_brw.reflect_branch.as_component_unchecked(); + + let refl_data = script_brw.data.unwrap(); + reflect.bundle_insert(&mut this.0, refl_data); + + Ok(()) + }); + } +} \ No newline at end of file diff --git a/lyra-scripting/src/lua/script.rs b/lyra-scripting/src/lua/script.rs index e74efdd..cbf7258 100644 --- a/lyra-scripting/src/lua/script.rs +++ b/lyra-scripting/src/lua/script.rs @@ -1,46 +1,41 @@ use std::sync::Mutex; -use tracing::debug; +use elua::{AsLua, StdLibraries}; -use crate::{ScriptHost, ScriptError, ScriptWorldPtr}; +use crate::{ScriptHost, ScriptError, ScriptWorldPtr, ScriptEntity}; #[derive(Default)] pub struct LuaHost; -fn try_call_lua_function(lua: &mlua::Lua, fn_name: &str) -> Result<(), ScriptError> { - let globals = lua.globals(); - - match globals.get::<_, mlua::Function>(fn_name) { - Ok(init_fn) => { - init_fn.call(()) - .map_err(ScriptError::MluaError)?; - }, - Err(mlua::Error::FromLuaConversionError { from: "nil", to: "function", message: None }) => { - debug!("Function '{}' was not found, ignoring...", fn_name) - // ignore - }, - Err(e) => { - return Err(ScriptError::MluaError(e)); - }, +fn try_call_lua_function(lua: &elua::State, fn_name: &str) -> Result<(), ScriptError> { + let globals = lua.globals()?; + + if globals.has_key(fn_name)? { + let lua_fn = globals.get::<_, elua::Function>(fn_name)?; + lua_fn.exec(()) + .map_err(ScriptError::MluaError)?; } Ok(()) } impl ScriptHost for LuaHost { - type ScriptContext = Mutex; + type ScriptContext = Mutex; fn load_script(&mut self, script: &[u8], script_data: &crate::ScriptData, providers: &mut crate::ScriptApiProviders) -> Result { - let mut ctx = Mutex::new(mlua::Lua::new()); - + let mut ctx = Mutex::new({ + let s = elua::State::new(); + s.expose_libraries(StdLibraries::all()); + s + }); + for provider in providers.apis.iter_mut() { - provider.expose_api(&mut ctx)?; + provider.expose_api(script_data, &mut ctx)?; } let lua = ctx.lock().unwrap(); - lua.load(script) - .set_name(&script_data.name) - .exec() + lua.load(&script_data.name, script)? + .execute(()) .map_err(|e| ScriptError::MluaError(e))?; drop(lua); @@ -52,22 +47,26 @@ impl ScriptHost for LuaHost { provider.setup_script(script_data, ctx)?; } - let ctx = ctx.lock().expect("Failure to get Lua ScriptContext"); - try_call_lua_function(&ctx, "init")?; - Ok(()) } /// Runs the update step of the lua script. /// - /// It looks for an `update` function with zero parameters in [`the ScriptContext`] and executes it. - fn update_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData, ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders) -> Result<(), ScriptError> { + /// It looks for an `update` function with zero parameters in the [`ScriptContext`] and executes it. + fn call_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData, + ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders, + function_name: &str) -> Result<(), ScriptError> { for provider in providers.apis.iter_mut() { provider.update_script_environment(world.clone(), script_data, ctx)?; } - + let ctx = ctx.lock().expect("Failure to get Lua ScriptContext"); - try_call_lua_function(&ctx, "update")?; + + let globals = ctx.globals()?; + globals.set("world", world.as_lua(&ctx)?)?; + globals.set("entity", ScriptEntity(script_data.entity).as_lua(&ctx)?)?; + + try_call_lua_function(&ctx, function_name)?; Ok(()) } diff --git a/lyra-scripting/src/lua/system.rs b/lyra-scripting/src/lua/system.rs new file mode 100644 index 0000000..7b0effc --- /dev/null +++ b/lyra-scripting/src/lua/system.rs @@ -0,0 +1,258 @@ +use anyhow::anyhow; +use lyra_ecs::{query::{Entities, ResMut, View}, World}; +use lyra_game::{game::GameStages, plugin::Plugin}; +use lyra_reflect::TypeRegistry; +use lyra_resource::ResourceManager; +use tracing::{debug, debug_span, error, trace}; + +use crate::{GameScriptExt, ScriptApiProviders, ScriptContexts, ScriptData, ScriptError, ScriptHost, ScriptList, ScriptWorldPtr}; + +use super::{providers::{LyraEcsApiProvider, LyraMathApiProvider, UtilityApiProvider}, LuaContext, LuaHost, LuaLoader, LuaScript}; + +/// A system that creates the script contexts in the world as new scripts are found +pub fn lua_scripts_create_contexts( + world: &mut World, + mut host: ResMut, + mut contexts: ResMut>, + mut providers: ResMut>, + view: View<(Entities, &ScriptList)>, +) -> anyhow::Result<()> { + for (en, scripts) in view.into_iter() { + for script in scripts.iter() { + if !contexts.has_context(script.id()) { + let script_data = ScriptData { + name: script.name().to_string(), + script_id: script.id(), + entity: en, + }; + + let script_name = script.name(); + let _span = debug_span!("lua", script = script_name).entered(); + + if let Some(script_res) = &script.res_handle().try_data_ref() { + debug!("Loading script..."); + let mut script_ctx = + host.load_script(&script_res.bytes, &script_data, &mut providers)?; + trace!("Finished loading script"); + + debug!("Setting up script..."); + host.setup_script(&script_data, &mut script_ctx, &mut providers)?; + trace!("Finished setting up script"); + + // call on_init, handle the error + let world_ptr = ScriptWorldPtr::from_ref(&world); + match host.call_script( + world_ptr, + &script_data, + &mut script_ctx, + &mut providers, + "on_init", + ) { + Ok(()) => {} + Err(e) => match e { + ScriptError::MluaError(m) => { + error!("Script '{}' ran into an error: {}", script.name(), m); + } + ScriptError::Other(_) => return Err(e.into()), + }, + } + + contexts.add_context(script.id(), script_ctx); + break; + } else { + trace!("Script is not loaded yet, skipping for now"); + } + } + } + } + + Ok(()) +} + +/// A system that triggers a reload of watched script resources. +/// +/// Note: This only works if the script is watched. See [`lyra_resource::ResourceManager::watch`]. +pub fn lua_scripts_reload_system( + mut contexts: ResMut>, + mut resman: ResMut, + view: View<&ScriptList>, +) -> anyhow::Result<()> { + for scripts in view.into_iter() { + for script in scripts.iter() { + let handle = script.res_handle(); + if handle.is_watched() { + let handle_path = handle.path(); + let watch_recv = resman.watcher_event_recv(&handle_path).unwrap(); + + match watch_recv.try_recv() { + Ok(ev) => { + let evs = + ev.map_err(|e| anyhow!("Script watcher ran into errors: {:?}", e))?; + + if evs.iter().any(|ev| ev.event.kind.is_modify()) { + debug!( + "Detected change of '{}' script, triggering reload", + handle_path + ); + + contexts.remove_context(script.id()).unwrap(); + resman.reload(handle)?; + } + } + Err(e) => match e { + lyra_resource::channel::TryRecvError::Empty => {} + lyra_resource::channel::TryRecvError::Disconnected => { + resman.stop_watching(&handle_path).unwrap(); + } + }, + } + } + } + } + + Ok(()) +} + +fn lua_call_script_function(world: &mut World, stage_name: &str) -> anyhow::Result<()> { + let world_ptr = ScriptWorldPtr::from_ref(&world); + let mut host = world.get_resource_mut::(); + let mut contexts = world.get_resource_mut::>(); + let mut providers = world.get_resource_mut::>(); + + for (en, scripts) in world.view_iter::<(Entities, &ScriptList)>() { + for script in scripts.iter() { + let script_data = ScriptData { + name: script.name().to_string(), + script_id: script.id(), + entity: en, + }; + + if let Some(ctx) = contexts.get_context_mut(script.id()) { + trace!( + "Running '{}' function in script '{}'", + stage_name, + script.name() + ); + + match host.call_script( + world_ptr.clone(), + &script_data, + ctx, + &mut providers, + stage_name, + ) { + Ok(()) => {} + Err(e) => match e { + ScriptError::MluaError(m) => { + error!("Script '{}' ran into an error: {}", script.name(), m); + } + ScriptError::Other(_) => return Err(e.into()), + }, + } + } + } + } + + Ok(()) +} + +/// This system executes the 'on_update' function of lua scripts in the world. It is meant to run +/// during the 'GameStages::Update' stage. +pub fn lua_script_update_stage_system(world: &mut World) -> anyhow::Result<()> { + lua_call_script_function(world, "on_update") +} + +/// This system executes the 'on_pre_update' function of lua scripts in the world. It is meant to run +/// during the 'GameStages::PreUpdate' stage. +pub fn lua_script_pre_update_stage_system(world: &mut World) -> anyhow::Result<()> { + lua_call_script_function(world, "on_pre_update") +} + +/// This system executes the 'on_post_update' function of lua scripts in the world. It is meant to run +/// during the 'GameStages::PostUpdate' stage. +pub fn lua_script_post_update_stage_system(world: &mut World) -> anyhow::Result<()> { + lua_call_script_function(world, "on_post_update") +} + +/// This system executes the 'on_first' function of lua scripts in the world. It is meant to run +/// during the 'GameStages::First' stage. +pub fn lua_script_first_stage_system(world: &mut World) -> anyhow::Result<()> { + lua_call_script_function(world, "on_first") +} + +/// This system executes the 'on_last' function of lua scripts in the world. It is meant to run +/// during the 'GameStages::Last' stage. +pub fn lua_script_last_stage_system(world: &mut World) -> anyhow::Result<()> { + lua_call_script_function(world, "on_last") +} + +#[derive(Default)] +pub struct LuaScriptingPlugin; + +impl Plugin for LuaScriptingPlugin { + fn setup(&self, game: &mut lyra_game::game::Game) { + let world = game.world_mut(); + + world.add_resource_default::(); + + world.add_resource_default::(); + world.add_resource_default::>(); + world.add_resource_default::>(); + + let mut loader = world + .try_get_resource_mut::() + .expect("Add 'ResourceManager' to the world before trying to add this plugin"); + loader.register_loader::(); + drop(loader); + + game.add_script_api_provider::(UtilityApiProvider); + game.add_script_api_provider::(LyraEcsApiProvider); + game.add_script_api_provider::(LyraMathApiProvider); + + game.add_system_to_stage( + GameStages::First, + "lua_create_contexts", + lua_scripts_create_contexts, + &[], + ) + .add_system_to_stage( + GameStages::First, + "lua_reload_scripts", + lua_scripts_reload_system, + &["lua_create_contexts"], + ) + .add_system_to_stage( + GameStages::First, + "lua_first_stage", + lua_script_first_stage_system, + &["lua_reload_scripts"], + ) + // cannot depend on 'lua_create_contexts' since it will cause a panic. + // the staged executor separates the executor of a single stage so this system + // cannot depend on the other one. + .add_system_to_stage( + GameStages::PreUpdate, + "lua_pre_update", + lua_script_pre_update_stage_system, + &[], + ) + .add_system_to_stage( + GameStages::Update, + "lua_update", + lua_script_update_stage_system, + &[], + ) + .add_system_to_stage( + GameStages::PostUpdate, + "lua_post_update", + lua_script_post_update_stage_system, + &[], + ) + .add_system_to_stage( + GameStages::Last, + "lua_last_stage", + lua_script_last_stage_system, + &[], + ); + } +} diff --git a/lyra-scripting/src/lua/test.rs b/lyra-scripting/src/lua/test.rs deleted file mode 100644 index 398d7ce..0000000 --- a/lyra-scripting/src/lua/test.rs +++ /dev/null @@ -1,219 +0,0 @@ -use std::{sync::{Mutex, Arc}, alloc::Layout, ptr::NonNull}; - -use lyra_ecs::{World, query::{Res, View, Entities, ResMut}, Component, system::{GraphExecutor, IntoSystem}}; -use lyra_resource::ResourceManager; -use mlua::{IntoLua, AnyUserDataExt}; -use tracing::{debug, error}; -use tracing_subscriber::{layer::SubscriberExt, fmt, filter, util::SubscriberInitExt}; - -use crate::{ScriptHost, ScriptData, ScriptApiProvider, ScriptApiProviders, ScriptError, ScriptWorldPtr, ScriptList, Script, ScriptContexts}; - -use super::{LuaHost, LuaLoader, LuaScript}; - -use crate::lyra_engine; - -fn enable_tracing() { - tracing_subscriber::registry() - .with(fmt::layer().with_writer(std::io::stdout)) - .init(); -} - -#[derive(Default)] -struct PrintfProvider; - -impl ScriptApiProvider for PrintfProvider { - type ScriptContext = Mutex; - - fn expose_api(&mut self, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { - let ctx = ctx.lock().unwrap(); - - fn printf(lua: &mlua::Lua, (mut text, formats): (String, mlua::Variadic)) -> mlua::Result<()> { - let mut formatted = String::new(); - let mut arg_num = 0; - - while let Some(start) = text.find("{}") { - let val_str = match formats.get(arg_num) { - Some(v) => match v { - mlua::Value::Nil => "nil".to_string(), - mlua::Value::Boolean(b) => b.to_string(), - mlua::Value::LightUserData(_) => { - return Err(mlua::Error::RuntimeError(format!("unable to get string representation of LightUserData"))); - }, - mlua::Value::Integer(i) => i.to_string(), - mlua::Value::Number(n) => n.to_string(), - mlua::Value::String(s) => s.to_str().unwrap().to_string(), - mlua::Value::Table(_) => { - return Err(mlua::Error::RuntimeError(format!("unable to get string representation of Table"))); - }, - mlua::Value::Function(_) => { - return Err(mlua::Error::RuntimeError(format!("unable to get string representation of Function"))); - }, - mlua::Value::Thread(_) => { - return Err(mlua::Error::RuntimeError(format!("unable to get string representation of Thread"))); - }, - mlua::Value::UserData(ud) => { - if let Ok(tos) = ud.get::<_, mlua::Function>(mlua::MetaMethod::ToString.to_string()) { - tos.call::<_, String>(())? - } else { - return Err(mlua::Error::RuntimeError(format!("UserData does not implement MetaMethod '__tostring'"))); - } - }, - mlua::Value::Error(e) => e.to_string(), - }, - None => { - let got_args = arg_num;// - 1; - - // continue searching for {} to get the number of format spots for the error message. - while let Some(start) = text.find("{}") { - text = text[start + 2..].to_string(); - arg_num += 1; - } - - return Err(mlua::Error::BadArgument { - to: Some("printf".to_string()), - pos: 2, - name: Some("...".to_string()), - cause: Arc::new(mlua::Error::RuntimeError(format!("not enough args \ - given for the amount of format areas in the string. Expected {}, \ - got {}.", arg_num, got_args))) - }) - }, - }; - - formatted = format!("{}{}{}", formatted, &text[0..start], val_str); - - text = text[start + 2..].to_string(); - - arg_num += 1; - } - - if arg_num < formats.len() { - return Err(mlua::Error::BadArgument { - to: Some("printf".to_string()), - pos: 2, - name: Some("...".to_string()), - cause: Arc::new(mlua::Error::RuntimeError(format!("got more args \ - than format areas in the string. Expected {}, got {}.", formats.len(), arg_num))) - }) - } - - formatted = format!("{}{}", formatted, text); - - lua.globals() - .get::<_, mlua::Function>("print") - .unwrap() - .call::<_, ()>(formatted) - .unwrap(); - - Ok(()) - } - - let printf_func = ctx.create_function(printf).unwrap(); - - let globals = ctx.globals(); - globals.set("printf", printf_func).unwrap(); - - Ok(()) - } - - fn setup_script(&mut self, _data: &ScriptData, _ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { - Ok(()) - } - - fn update_script_environment(&mut self, world: crate::ScriptWorldPtr, _data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> { - let ctx = ctx.lock().unwrap(); - let globals = ctx.globals(); - - let world_lua = world.into_lua(&ctx) - .map_err(ScriptError::MluaError)?; - globals.set("world", world_lua) - .map_err(ScriptError::MluaError)?; - - Ok(()) - } -} - -/// Tests a simple lua script that just prints some test -#[test] -pub fn lua_print() { - enable_tracing(); - - let mut world = World::new(); - - let test_provider = PrintfProvider::default(); - let mut providers = ScriptApiProviders::::default(); - providers.add_provider(test_provider); - - let host = LuaHost::default(); - - world.add_resource(host); - world.add_resource(providers); - world.add_resource(ScriptContexts::>::default()); - - let mut res_loader = ResourceManager::new(); - res_loader.register_loader::(); - - let script = - r#" - print("Hello World") - - function update() - print("updated") - printf("I love to eat formatted {}!", "food") - --printf("World is {}", world) - end - "#; - let script = script.as_bytes(); - - let script = res_loader.load_bytes::("test_script.lua", - "text/lua", script.to_vec(), 0, script.len()).unwrap(); - let script = Script::new("text_script.lua", script); - - let scripts = ScriptList::new(vec![script]); - - world.spawn((scripts,)); - - let mut exec = GraphExecutor::new(); - exec.insert_system("lua_update_scripts", lua_update_scripts.into_system(), &[]); - exec.execute(NonNull::from(&world), true).unwrap(); -} - -fn lua_update_scripts(world: &mut World) -> anyhow::Result<()> { - let world_ptr = ScriptWorldPtr::from_ref(&world); - let mut host = world.get_resource_mut::(); - let mut contexts = world.get_resource_mut::>>(); - let mut providers = world.get_resource_mut::>(); - - for scripts in world.view_iter::<&ScriptList>() { - for script in scripts.iter() { - let script_data = ScriptData { - name: script.name().to_string(), - script_id: script.id(), - }; - - if !contexts.has_context(script.id()) { - if let Some(script_res) = &script.res_handle().data { - let mut script_ctx = host.load_script(&script_res.bytes, &script_data, &mut providers).unwrap(); - host.setup_script(&script_data, &mut script_ctx, &mut providers).unwrap(); - contexts.add_context(script.id(), script_ctx); - } else { - debug!("Script '{}' is not yet loaded, skipping", script.name()); - } - } - - let ctx = contexts.get_context_mut(script.id()).unwrap(); - - match host.update_script(world_ptr.clone(), &script_data, ctx, &mut providers) { - Ok(()) => {}, - Err(e) => match e { - ScriptError::MluaError(m) => { - error!("Script '{}' ran into an error: {}", script.name(), m); - }, - ScriptError::Other(_) => return Err(e.into()), - }, - } - } - } - - Ok(()) -} \ No newline at end of file diff --git a/lyra-scripting/src/lua/world.rs b/lyra-scripting/src/lua/world.rs index a6c6552..848bd86 100644 --- a/lyra-scripting/src/lua/world.rs +++ b/lyra-scripting/src/lua/world.rs @@ -1,22 +1,36 @@ -use lyra_ecs::query::dynamic::QueryDynamicType; -use lyra_reflect::TypeRegistry; -use mlua::{AnyUserDataExt, IntoLua, IntoLuaMulti}; +use std::{ptr::NonNull, sync::Arc}; -use crate::{ScriptWorldPtr, ScriptEntity, ScriptDynamicBundle, ScriptBorrow}; +use crate::{ScriptBorrow, ScriptEntity, ScriptWorldPtr}; +use elua::AsLua; +use lyra_ecs::{query::dynamic::QueryDynamicType, CommandQueue, Commands, DynamicBundle, World}; +use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry}; +use lyra_resource::ResourceManager; -use super::{ReflectedIterator, DynamicViewIter, FN_NAME_INTERNAL_REFLECT_TYPE, reflect_user_data, ReflectLuaProxy}; +use super::{ + reflect_user_data, wrappers::LuaResHandle, DynamicViewIter, LuaTableProxyLookup, ReflectLuaProxy, ReflectedIterator, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE +}; -impl<'lua> mlua::FromLua<'lua> for ScriptEntity { - fn from_lua(value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result { +impl<'lua> elua::FromLua<'lua> for ScriptEntity { + fn from_lua(_: &'lua elua::State, value: elua::Value<'lua>) -> elua::Result { match value { - mlua::Value::UserData(ud) => Ok(ud.borrow::()?.clone()), - mlua::Value::Nil => Err(mlua::Error::FromLuaConversionError { from: "Nil", to: "ScriptEntity", message: Some("Value was nil".to_string()) }), + elua::Value::Userdata(ud) => Ok(ud.as_ref::()?.clone()), + elua::Value::Nil => Err(elua::Error::type_mismatch("ScriptEntity", "Nil")), _ => panic!(), } } } -impl mlua::UserData for ScriptEntity {} +impl elua::Userdata for ScriptEntity { + fn name() -> String { + "Entity".to_string() + } + + fn build<'a>(builder: &mut elua::userdata::UserdataBuilder<'a, Self>) { + builder.meta_method(elua::MetaMethod::ToString, |_, this, ()| { + Ok(format!("{:?}", this.0)) + }); + } +} #[derive(thiserror::Error, Debug, Clone)] pub enum WorldError { @@ -24,108 +38,271 @@ pub enum WorldError { LuaInvalidUsage(String), } -impl mlua::UserData for ScriptWorldPtr { - fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) { - methods.add_method_mut("spawn", |_, this, (bundle,): (ScriptDynamicBundle,)| { - let world = unsafe { this.inner.as_mut() }; - - Ok(ScriptEntity(world.spawn(bundle.0))) - }); +impl<'a> elua::FromLua<'a> for ScriptWorldPtr { + fn from_lua(_: &'a elua::State, val: elua::Value<'a>) -> elua::Result { + match val { + elua::Value::Userdata(ud) => Ok(ud.as_ref::()?.clone()), + elua::Value::Nil => Err(elua::Error::type_mismatch("ScriptWorldPtr", "Nil")), + _ => panic!(), + } + } +} - methods.add_method("view_iter", |lua, this, queries: mlua::Variadic| { - let world = unsafe { this.inner.as_ref() }; - let mut view = world.dynamic_view(); +impl elua::Userdata for ScriptWorldPtr { + fn name() -> String { + "World".to_string() + } - for comp in queries.into_iter() { - let script_brw = comp.call_function::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ()) - .expect("Type does not implement '__internal_reflect_type' properly"); - let refl_comp = script_brw.reflect_branch.as_component_unchecked(); + fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) { + builder + .method_mut("spawn", |_, this, vals: elua::ValueVec| { + let world = this.as_mut(); - let dyn_type = QueryDynamicType::from_info(refl_comp.info); - view.push(dyn_type); - } + let mut bundle = DynamicBundle::new(); - let iter = view.into_iter(); - let mut reflected_iter = ReflectedIterator { - world: this.clone(), - dyn_view: DynamicViewIter::from(iter), - reflected_components: None, - }; + //while let Some(val) = vals.pop_front() { + for (i, val) in vals.into_iter().enumerate() { + let ud = val.as_userdata().ok_or( + elua::Error::bad_arg( + Some("World:spawn"), + 2 + i as i32, // i starts at 0 + Some("components..."), + Arc::new(elua::Error::runtime("provided component is not userdata")), + ))?; + + let comp_borrow = { + if let Ok(as_comp) = ud.get::<_, elua::Function>(FN_NAME_INTERNAL_AS_COMPONENT) { + let ud = match as_comp.exec(ud.clone())? { + elua::Value::Userdata(ud) => ud, + elua::Value::Nil => ud.clone(), + _ => todo!(), + }; - let f = lua.create_function_mut(move |lua, ()| { - if let Some(row) = reflected_iter.next_lua(lua) { - let row = row.into_iter().map(|(_, ud)| ud.into_lua(lua)) - .collect::>>()?; - Ok(mlua::MultiValue::from_vec(row)) - } else { - Ok(mlua::Value::Nil.into_lua_multi(lua)?) + ud.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())? + } else { + ud.execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())? + } + }; + + let reflect = comp_borrow.reflect_branch.as_component_unchecked(); + let refl_data = comp_borrow.data.unwrap(); + reflect.bundle_insert(&mut bundle, refl_data); } - })?; - Ok(f) - }); + // defer the entity spawn + // safety: Commands borrows Entities from World, the resource borrows from the world resouces, + // they are borrowing different parts of World. + let world_ptr: *mut World = world; + let mut commands_queue = world.get_resource_mut::(); + let mut commands = Commands::new(&mut commands_queue, unsafe { &mut *world_ptr }); + let entity = commands.spawn(bundle); - methods.add_method("view", |lua, this, (system, queries): (mlua::Function, mlua::Variadic)| { - if queries.is_empty() { - panic!("No components were provided!"); - } - - let world = unsafe { this.inner.as_ref() }; - let mut view = world.dynamic_view(); + Ok(ScriptEntity(entity)) + }) + .method_mut( + "view", + |lua, this, (system, queries): (elua::Function, elua::ValueVec)| { + if queries.is_empty() { + return Err(elua::Error::BadArgument { + func: Some("World:view".to_string()), + arg_index: 2, + arg_name: Some("query...".to_string()), + error: Arc::new(elua::Error::other(WorldError::LuaInvalidUsage( + "no component types provided".to_string(), + ))), + }); + } - for comp in queries.into_iter() { - let reflect = comp.call_function::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ()) - .expect("Type does not implement 'reflect_type' properly"); - let refl_comp = reflect.reflect_branch.as_component_unchecked(); + let world = unsafe { this.inner.as_ref() }; + let mut view = world.dynamic_view(); - let dyn_type = QueryDynamicType::from_info(refl_comp.info); - view.push(dyn_type); - } + for (idx, comp) in queries.into_iter().enumerate() { + match comp { + elua::Value::Table(t) => { + let name: String = t.get(elua::MetaMethod::Name)?; - let iter = view.into_iter(); - let mut reflected_iter = ReflectedIterator { - world: this.clone(), - dyn_view: DynamicViewIter::from(iter), - reflected_components: None, - }; + let lookup = world + .try_get_resource::() + .ok_or(elua::Error::runtime( + "Unable to lookup table proxy, none were ever registered!", + ))?; + let info = lookup.comp_info_from_name.get(&name).ok_or_else( + || elua::Error::BadArgument { + func: Some("World:view".to_string()), + arg_index: 2 + idx as i32, + arg_name: Some("query...".to_string()), + error: Arc::new(elua::Error::Runtime(format!( + "the 'Table' with name {} is unknown to the engine!", + name + ))), + }, + )?; - let reg = this.as_ref().get_resource::(); - - while let Some(row) = reflected_iter.next_lua(lua) { - let (reflects, values): (Vec<(_, _)>, Vec<_>) = row.into_iter().unzip(); - - let value_row: Vec<_> = values.into_iter().map(|ud| ud.into_lua(lua)).collect::>>()?; - let mult_val = mlua::MultiValue::from_vec(value_row); - let res: mlua::MultiValue = system.call(mult_val)?; - - // if values were returned, find the type in the type registry, and apply the new values - if res.len() <= reflects.len() { - for (i, comp) in res.into_iter().enumerate() { - let (_proxy, ptr) = reflects[i]; - - match comp.as_userdata() { - Some(ud) => { - let lua_comp = reflect_user_data(ud); - let refl_comp = lua_comp.reflect_branch.as_component_unchecked(); - let lua_typeid = refl_comp.info.type_id.as_rust(); - let reg_type = reg.get_type(lua_typeid).unwrap(); - - let proxy = reg_type.get_data::().unwrap(); - (proxy.fn_apply)(lua, ptr, ud)? + let dyn_type = QueryDynamicType::from_info(info.clone()); + view.push(dyn_type); } - None => { - panic!("A userdata value was not returned!"); + elua::Value::Userdata(ud) => { + let reflect = ud + .execute_function::<_, ScriptBorrow>( + FN_NAME_INTERNAL_REFLECT_TYPE, + (), + ) + .expect("Type does not implement 'reflect_type' properly"); + let refl_comp = reflect.reflect_branch.as_component_unchecked(); + + let dyn_type = QueryDynamicType::from_info(refl_comp.info); + view.push(dyn_type); } + _ => todo!(), } } - } else { - let msg = format!("Too many arguments were returned from the World view! - At most, the expected number of results is {}.", reflects.len()); - return Err(mlua::Error::external(WorldError::LuaInvalidUsage(msg))); - } - } - Ok(()) - }); + let iter = view.into_iter(); + let mut reflected_iter = ReflectedIterator { + world: this.clone(), + dyn_view: DynamicViewIter::from(iter), + reflected_components: None, + }; + + let mut current = world.current_tick(); + let mut has_ticked = false; + + while let Some(row) = reflected_iter.next_lua(lua) { + let r = row + .row + .into_iter() + .map(|r| (r.comp_val, r.comp_ptr.cast::<()>())) + .collect::>(); + let (values, ptrs) = + itertools::multiunzip::<(Vec, Vec>), _>(r); + let mult_val = elua::ValueVec::from(values); + let res: elua::ValueVec = system.exec(mult_val)?; + + // if values were returned, find the type in the type registry, and apply the new values + if res.len() <= ptrs.len() { + // we only want to tick one time per system + if !has_ticked { + current = world.tick(); + has_ticked = true; + } + + for (comp, ptr) in res.into_iter().zip(ptrs) { + let lua_typeid = match &comp { + elua::Value::Userdata(ud) => { + let lua_comp = reflect_user_data(ud); + let refl_comp = + lua_comp.reflect_branch.as_component_unchecked(); + refl_comp.info.type_id.as_rust() + } + elua::Value::Table(tbl) => { + let name: String = tbl.get(elua::MetaMethod::Name)?; + + let lookup = world.get_resource::(); + *lookup.typeid_from_name.get(&name).unwrap() + } + _ => { + panic!("A userdata or table value was not returned!"); + // TODO: Handle properly + } + }; + + // update the component tick + let world = unsafe { this.inner.as_mut() }; + let arch = world.entity_archetype_mut(row.entity).unwrap(); + let idx = arch.entities().get(&row.entity).unwrap().clone(); + let c = arch.get_column_mut(lua_typeid.into()).unwrap(); + c.entity_ticks[idx.0 as usize] = current; + + // apply the new component data + let reg = this.as_ref().get_resource::(); + let reg_type = reg.get_type(lua_typeid).unwrap(); + + let proxy = reg_type + .get_data::() + // this should actually be safe since the ReflectedIterator + // attempts to get the type data before it is tried here + .expect("Type does not have ReflectLuaProxy as a TypeData"); + (proxy.fn_apply)(lua, ptr, &comp)?; + } + } else { + let msg = format!( + "Too many arguments were returned from the World view! + At most, the expected number of results is {}.", + ptrs.len() + ); + return Err(elua::Error::Runtime(msg)); + } + } + + Ok(()) + }, + ) + .method_mut("resource", |lua, this, (ty,): (elua::Value,)| { + let reflect = match ty { + elua::Value::Userdata(ud) => ud + .execute_function::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ()) + .expect("Type does not implement 'reflect_type' properly"), + elua::Value::Table(t) => { + let f: elua::Function = t.get(FN_NAME_INTERNAL_REFLECT_TYPE)?; + f.exec::<_, ScriptBorrow>(()) + .expect("Type does not implement 'reflect_type' properly") + } + _ => { + panic!("how"); + } + }; + /* let reflect = ty + .execute_function::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ()) + .expect("Type does not implement 'reflect_type' properly"); */ + + let res = reflect.reflect_branch.as_resource_unchecked(); + if let Some(res_ptr) = res.reflect_ptr(this.as_mut()) { + let reg_type = this + .as_ref() + .get_type::(reflect.reflect_branch.reflect_type_id()) + .expect("Resource is not type registered!"); + let proxy = reg_type + .get_data::() + .expect("Type does not have ReflectLuaProxy as a TypeData"); + + (proxy.fn_as_lua)(lua, res_ptr.cast()).and_then(|ud| ud.as_lua(lua)) + } else { + // if the resource is not found in the world, return nil + Ok(elua::Value::Nil) + } + }) + .method_mut("add_resource", |_, this, res: elua::Value| { + let reflect = match res { + elua::Value::Userdata(ud) => ud + .execute_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ()) + .expect("Type does not implement 'reflect_type' properly"), + elua::Value::Table(t) => { + let f: elua::Function = t.get(FN_NAME_INTERNAL_REFLECT)?; + f.exec::<_, ScriptBorrow>(()) + .expect("Type does not implement 'reflect_type' properly") + } + _ => { + panic!("how"); + } + }; + + let data = reflect.data + .expect("Its expected that 'FN_NAME_INTERNAL_REFLECT' returns data in World:add_resource"); + + let res = reflect.reflect_branch.as_resource() + .ok_or(elua::Error::runtime("Provided type is not a resource!"))?; + + let world = this.as_mut(); + res.insert(world, data); + + Ok(()) + }) + .method_mut("request_res", |_, this, path: String| { + let world = this.as_mut(); + let mut man = world.get_resource_mut::(); + let handle = man.request_raw(&path).unwrap(); + + Ok(LuaResHandle::from(handle)) + }); } -} \ No newline at end of file +} diff --git a/lyra-scripting/src/lua/wrappers/delta_time.rs b/lyra-scripting/src/lua/wrappers/delta_time.rs new file mode 100644 index 0000000..027985c --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/delta_time.rs @@ -0,0 +1,40 @@ + +use std::any::TypeId; + +use lyra_game::DeltaTime; +use crate::lua::LuaWrapper; + +#[derive(Clone, Default)] +pub struct LuaDeltaTime(pub(crate) DeltaTime); + +impl std::ops::Deref for LuaDeltaTime { + type Target = DeltaTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for LuaDeltaTime { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'lua> elua::FromLua<'lua> for LuaDeltaTime { + fn from_lua(_: &'lua elua::State, _: elua::Value<'lua>) -> elua::Result { + todo!() + } +} + +impl<'lua> elua::AsLua<'lua> for LuaDeltaTime { + fn as_lua(self, _: &'lua elua::State) -> elua::Result> { + Ok(elua::Value::Number(*self.0 as f64)) + } +} + +impl LuaWrapper for LuaDeltaTime { + fn wrapped_type_id() -> std::any::TypeId { + TypeId::of::() + } +} \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/input_actions.rs b/lyra-scripting/src/lua/wrappers/input_actions.rs new file mode 100644 index 0000000..410b83c --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/input_actions.rs @@ -0,0 +1,185 @@ +use lyra_game::input::{keycode_from_str, Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, ActionState, LayoutId, MouseAxis, MouseInput}; +use crate::lyra_engine; + +use lyra_reflect::Reflect; + +use crate::{lua::{LuaWrapper, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptBorrow}; + +#[derive(Clone, Reflect)] +pub struct LuaActionHandler { + handler: ActionHandler +} + +impl elua::Userdata for LuaActionHandler { + fn name() -> String { + "ActionHandler".to_string() + } + + fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) { + builder.function("new", |_, table: elua::Table| { + let mut handler = ActionHandler::new(); + + // create the layouts and add them to the handler + let layouts = table.get::<_, elua::Table>("layouts") + .map_err(|_| elua::Error::runtime("missing 'layouts' in ActionHandler table"))?; + for layout_id in layouts.sequence_iter::() { + let layout_id = layout_id?; + + handler.add_layout(LayoutId(layout_id)); + } + + // add the actions to the handler + let actions = table.get::<_, elua::Table>("actions") + .map_err(|_| elua::Error::runtime("missing 'actions' in ActionHandler table"))?; + for pair in actions.pairs::() { + let (action_lbl, action_type) = pair?; + let action_type = action_type.to_lowercase(); + + let action_type = match action_type.as_str() { + "axis" => ActionKind::Axis, + "button" => ActionKind::Button, + _ => todo!("Handle invalid axis type"), + }; + + handler.add_action(action_lbl, Action::new(action_type)); + } + + // find the mappings and start processing them + let mappings= table.get::<_, elua::Table>("mappings") + .map_err(|_| elua::Error::runtime("missing 'mappings' in ActionHandler table"))?; + for (map_id, tbl) in mappings.sequence_iter::().enumerate() { + let tbl = tbl?; + + let layout_id = tbl.get::<_, u32>("layout")?; + let mut mapping = ActionMapping::new(LayoutId(layout_id), ActionMappingId(map_id as u32)); + + // find the binds and start processing them + // the keys are used as the action names, and then the value is an array (lua table) + let binds_tbl = tbl.get::<_, elua::Table>("binds") + .map_err(|_| elua::Error::runtime("missing 'binds' in ActionHandler 'mappings' table"))?; + for pair in binds_tbl.pairs::() { + let (action_lbl, input_binds) = pair?; + + for input in input_binds.sequence_iter::() { + let input = input?.to_lowercase(); + + let action = handler.action(&action_lbl) + .ok_or(elua::Error::Runtime(format!("Unknown action specified in mapping binds: {}", action_lbl)))?; + + let mut binds = Vec::new(); + + let input_split: Vec<&str> = input.split(":").collect(); + let input_name = input_split[0]; + let button = input_split[1]; + + if action.kind == ActionKind::Axis { + if button == "axis" { + let axis_name = input_split[2]; + + let src = process_axis_string(input_name, axis_name) + .ok_or(elua::Error::Runtime(format!("invalid bind '{input_name}', unable to find device or axis for device")))?; + binds.push(src.into_binding()); + } else { + // splits 'down=1' into 'down' and '1' + let (button, val_str) = button.split_once("=") + .ok_or(elua::Error::Runtime(format!("invalid bind string for Axis Action: '{input}' (expected '=' with float)")))?; + + let val = val_str.parse::() + .map_err(|e| elua::Error::Runtime(format!("invalid bind string for Axis Action: '{input}' ({e})")))?; + + let src = process_keyboard_string(button) + .ok_or(elua::Error::Runtime(format!("invalid key in bind: '{button}'")))?; + binds.push(src.into_binding_modifier(val)); + } + } else { + todo!("Process bindings for Button Actions"); + } + + mapping.bind(action_lbl.clone(), &binds); + } + } + + handler.add_mapping(mapping); + } + + Ok(LuaActionHandler { + handler, + }) + }) + .method("get_axis", |_, this, action: String| { + Ok(this.handler.get_axis_modifier(action)) + }) + .method("is_pressed", |_, this, action: String| { + Ok(this.handler.is_action_pressed(action)) + }) + .method("was_just_pressed", |_, this, action: String| { + Ok(this.handler.was_action_just_pressed(action)) + }) + .method("was_just_released", |_, this, action: String| { + Ok(this.handler.was_action_just_released(action)) + }) + .method("get_just_pressed", |_, this, action: String| { + Ok(this.handler.get_just_pressed_modifier(action)) + }) + .method("get_action_state", |lua, this, action: String| { + let state = this.handler.get_action_state(action); + + let (name, val) = match state { + ActionState::Idle => ("Idle", None), + ActionState::Pressed(v) => ("Pressed", Some(v)), + ActionState::JustPressed(v) => ("JustPressed", Some(v)), + ActionState::JustReleased => ("JustReleased", None), + ActionState::Axis(v) => ("Axis", Some(v)), + ActionState::Other(v) => ("Other", Some(v)), + }; + + let mut multi = elua::ValueVec::new(); + multi.push_val(lua, name)?; + multi.push_val(lua, val)?; + + Ok(elua::Value::Multi(multi)) + }) + .method(FN_NAME_INTERNAL_REFLECT, |_, this, ()| { + Ok(ScriptBorrow::from_resource::(Some(this.handler.clone()))) + }) + .function(FN_NAME_INTERNAL_REFLECT_TYPE, |_, ()| { + Ok(ScriptBorrow::from_resource::(None)) + }); + + } +} + +impl<'a> elua::FromLua<'a> for LuaActionHandler { + fn from_lua(_: &'a elua::State, val: elua::Value<'a>) -> elua::Result { + let tyname = val.type_name(); + let ud = val.as_userdata() + .ok_or(elua::Error::type_mismatch("ActionHandler", &tyname))?; + let handle = ud.as_ref::()?; + + Ok(handle.clone()) + } +} + +impl LuaWrapper for LuaActionHandler { + fn wrapped_type_id() -> std::any::TypeId { + std::any::TypeId::of::() + } +} + +fn process_keyboard_string(key_name: &str) -> Option { + let key = keycode_from_str(key_name)?; + + Some(ActionSource::Keyboard(key)) +} + +fn process_axis_string(device: &str, axis_name: &str) -> Option { + match device { + "mouse" => match axis_name { + "y" => Some(ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y))), + "x" => Some(ActionSource::Mouse(MouseInput::Axis(MouseAxis::X))), + "scroll" | "scrollwheel" => Some(ActionSource::Mouse(MouseInput::Axis(MouseAxis::ScrollWheel))), + _ => None, + }, + _ => None + } +} diff --git a/lyra-scripting/src/lua/wrappers/math.rs b/lyra-scripting/src/lua/wrappers/math.rs new file mode 100644 index 0000000..38cd41a --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/math.rs @@ -0,0 +1,649 @@ +use std::sync::Arc; + +use crate::lyra_engine; +use lyra_game::math; +use lyra_scripting_derive::wrap_math_vec_copy; + +use crate as lyra_scripting; + +// f32 types +wrap_math_vec_copy!( + math::Vec2, + derives(PartialEq), + fields(x, y), + metamethods( + Add(LuaVec2, f32), + Sub(LuaVec2, f32), + Div(LuaVec2, f32), + Mul(LuaVec2, f32), + Mod(LuaVec2, f32), + Eq, Unm, ToString + ), + custom_methods { + builder.method_mut("move_by", |lua, this, vals: elua::ValueVec| { + let vals_clone = vals.clone(); + if let Some((x, y)) = vals.try_into_vals::<(f32, f32)>(lua)? { + this.x += x; + this.y += y; + } else if let Some(v) = vals_clone.try_into_vals::(lua)? { + this.0 += v.0; + } + + Ok(()) + }); + } +); +wrap_math_vec_copy!( + math::Vec3, + derives(PartialEq), + fields(x, y, z), + metamethods( + Add(LuaVec3, f32), + Sub(LuaVec3, f32), + Div(LuaVec3, f32), + Mul(LuaVec3, f32), + Mod(LuaVec3, f32), + Eq, Unm, ToString + ), + custom_methods { + builder.method_mut("move_by", |lua, this, vals: elua::ValueVec| { + let vals_clone = vals.clone(); + if let Some((x, y, z)) = vals.try_into_vals::<(f32, f32, f32)>(lua)? { + this.x += x; + this.y += y; + this.z += z; + } else if let Some(v) = vals_clone.try_into_vals::(lua)? { + this.0 += v.0; + } + + Ok(()) + }); + } +); + +wrap_math_vec_copy!( + math::Vec4, + derives(PartialEq), + fields(w, x, y, z), + metamethods( + Add(LuaVec4, f32), + Sub(LuaVec4, f32), + Div(LuaVec4, f32), + Mul(LuaVec4, f32), + Mod(LuaVec4, f32), + Eq, Unm + ) +); + +// ================================================= + + +/* wrap_math_vec_copy!( + math::Vec3A, + derives(PartialEq), + fields(x, y, z), + metamethods( + Add(LuaVec3A, f32), + Sub(LuaVec3A, f32), + Div(LuaVec3A, f32), + Mul(LuaVec3A, f32), + Mod(LuaVec3A, f32), + Eq, Unm + ) +); + + +// f64 types +wrap_math_vec_copy!( + math::DVec2, + derives(PartialEq), + fields(x, y), + metamethods( + Add(LuaDVec2, f64), + Sub(LuaDVec2, f64), + Div(LuaDVec2, f64), + Mul(LuaDVec2, f64), + Mod(LuaDVec2, f64), + Eq, Unm + ) +); +wrap_math_vec_copy!( + math::DVec3, + derives(PartialEq), + fields(x, y, z), + metamethods( + Add(LuaDVec3, f64), + Sub(LuaDVec3, f64), + Div(LuaDVec3, f64), + Mul(LuaDVec3, f64), + Mod(LuaDVec3, f64), + Eq, Unm + ) +); +wrap_math_vec_copy!( + math::DVec4, + derives(PartialEq), + fields(w, x, y, z), + metamethods( + Add(LuaDVec4, f64), + Sub(LuaDVec4, f64), + Div(LuaDVec4, f64), + Mul(LuaDVec4, f64), + Mod(LuaDVec4, f64), + Eq, Unm + ) +); + +// i32 types +wrap_math_vec_copy!( + math::IVec2, + derives(PartialEq, Eq, Hash), + fields(x, y), + metamethods( + Add(LuaIVec2, i32), + Sub(LuaIVec2, i32), + Div(LuaIVec2, i32), + Mul(LuaIVec2, i32), + Mod(LuaIVec2, i32), + Shl(LuaIVec2, LuaUVec2, i32), + Shr(LuaIVec2, LuaUVec2, i32), + BAnd(LuaIVec2, i32), + BOr(LuaIVec2, i32), + BXor(LuaIVec2, i32), + Eq, Unm, BNot + ) +); +wrap_math_vec_copy!( + math::IVec3, + derives(PartialEq, Eq, Hash), + fields(x, y, z), + metamethods( + Add(LuaIVec3, i32), + Sub(LuaIVec3, i32), + Div(LuaIVec3, i32), + Mul(LuaIVec3, i32), + Mod(LuaIVec3, i32), + Shl(LuaIVec3, LuaUVec3, i32), + Shr(LuaIVec3, LuaUVec3, i32), + BAnd(LuaIVec3, i32), + BOr(LuaIVec3, i32), + BXor(LuaIVec3, i32), + Eq, Unm, BNot + ) +); +wrap_math_vec_copy!( + math::IVec4, + derives(PartialEq, Eq, Hash), + fields(w, x, y, z), + metamethods( + Add(LuaIVec4, i32), + Sub(LuaIVec4, i32), + Div(LuaIVec4, i32), + Mul(LuaIVec4, i32), + Mod(LuaIVec4, i32), + Shl(LuaIVec4, LuaUVec4, i32), + Shr(LuaIVec4, LuaUVec4, i32), + BAnd(LuaIVec4, i32), + BOr(LuaIVec4, i32), + BXor(LuaIVec4, i32), + Eq, Unm, BNot + ) +); + +// u32 types +wrap_math_vec_copy!( + math::UVec2, + derives(PartialEq, Eq, Hash), + fields(x, y), + metamethods( + Add(LuaUVec2, u32), + Sub(LuaUVec2, u32), + Div(LuaUVec2, u32), + Mul(LuaUVec2, u32), + Mod(LuaUVec2, u32), + Shl(LuaUVec2, LuaIVec2, i32), + Shr(LuaUVec2, LuaIVec2, i32), + BAnd(LuaUVec2, u32), + BOr(LuaUVec2, u32), + BXor(LuaUVec2, u32), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::UVec3, + derives(PartialEq, Eq, Hash), + fields(x, y, z), + metamethods( + Add(LuaUVec3, u32), + Sub(LuaUVec3, u32), + Div(LuaUVec3, u32), + Mul(LuaUVec3, u32), + Mod(LuaUVec3, u32), + Shl(LuaUVec3, LuaIVec3, i32), + Shr(LuaUVec3, LuaIVec3, i32), + BAnd(LuaUVec3, u32), + BOr(LuaUVec3, u32), + BXor(LuaUVec3, u32), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::UVec4, + derives(PartialEq, Eq, Hash), + fields(w, x, y, z), + metamethods( + Add(LuaUVec4, u32), + Sub(LuaUVec4, u32), + Div(LuaUVec4, u32), + Mul(LuaUVec4, u32), + Mod(LuaUVec4, u32), + Shl(LuaUVec4, LuaIVec4, i32), + Shr(LuaUVec4, LuaIVec4, i32), + BAnd(LuaUVec4, u32), + BOr(LuaUVec4, u32), + BXor(LuaUVec4, u32), + Eq, BNot + ) +); + +// i64 types +wrap_math_vec_copy!( + math::I64Vec2, + derives(PartialEq, Eq, Hash), + fields(x, y), + metamethods( + Add(LuaI64Vec2, i64), + Sub(LuaI64Vec2, i64), + Div(LuaI64Vec2, i64), + Mul(LuaI64Vec2, i64), + Mod(LuaI64Vec2, i64), + Shl(i64), + Shr(i64), + BAnd(LuaI64Vec2, i64), + BOr(LuaI64Vec2, i64), + BXor(LuaI64Vec2, i64), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::I64Vec3, + derives(PartialEq, Eq, Hash), + fields(x, y, z), + metamethods( + Add(LuaI64Vec3, i64), + Sub(LuaI64Vec3, i64), + Div(LuaI64Vec3, i64), + Mul(LuaI64Vec3, i64), + Mod(LuaI64Vec3, i64), + Shl(i64), + Shr(i64), + BAnd(LuaI64Vec3, i64), + BOr(LuaI64Vec3, i64), + BXor(LuaI64Vec3, i64), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::I64Vec4, + derives(PartialEq, Eq, Hash), + fields(w, x, y, z), + metamethods( + Add(LuaI64Vec4, i64), + Sub(LuaI64Vec4, i64), + Div(LuaI64Vec4, i64), + Mul(LuaI64Vec4, i64), + Mod(LuaI64Vec4, i64), + Shl(i64), + Shr(i64), + BAnd(LuaI64Vec4, i64), + BOr(LuaI64Vec4, i64), + BXor(LuaI64Vec4, i64), + Eq, BNot + ) +); + +// u64 types +wrap_math_vec_copy!( + math::U64Vec2, + derives(PartialEq, Eq, Hash), + fields(x, y), + metamethods( + Add(LuaU64Vec2, u64), + Sub(LuaU64Vec2, u64), + Div(LuaU64Vec2, u64), + Mul(LuaU64Vec2, u64), + Mod(LuaU64Vec2, u64), + Shl(i64), + Shr(i64), + BAnd(LuaU64Vec2, u64), + BOr(LuaU64Vec2, u64), + BXor(LuaU64Vec2, u64), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::U64Vec3, + derives(PartialEq, Eq, Hash), + fields(x, y, z), + metamethods( + Add(LuaU64Vec3, u64), + Sub(LuaU64Vec3, u64), + Div(LuaU64Vec3, u64), + Mul(LuaU64Vec3, u64), + Mod(LuaU64Vec3, u64), + Shl(i64), + Shr(i64), + BAnd(LuaU64Vec3, u64), + BOr(LuaU64Vec3, u64), + BXor(LuaU64Vec3, u64), + Eq, BNot + ) +); +wrap_math_vec_copy!( + math::U64Vec4, + derives(PartialEq, Eq, Hash), + fields(w, x, y, z), + metamethods( + Add(LuaU64Vec4, u64), + Sub(LuaU64Vec4, u64), + Div(LuaU64Vec4, u64), + Mul(LuaU64Vec4, u64), + Mod(LuaU64Vec4, u64), + Shl(i64), + Shr(i64), + BAnd(LuaU64Vec4, u64), + BOr(LuaU64Vec4, u64), + BXor(LuaU64Vec4, u64), + Eq, BNot + ) +);*/ + +// bool types +/* wrap_math_vec_copy!( + math::BVec2, + derives(PartialEq, Eq, Hash), + fields(x, y), + metamethods(Eq, BAnd, BOr, BXOr, BNot) +); +wrap_math_vec_copy!( + math::BVec3, + derives(PartialEq, Eq, Hash), + fields(x, y, z), + metamethods(Eq, BAnd, BOr, BXOr, BNot) +); +wrap_math_vec_copy!( + math::BVec4, + derives(PartialEq, Eq, Hash), + fields(w, x, y, z), + metamethods(Eq, BAnd, BOr, BXOr, BNot) +); */ + +// mat2 +/* wrap_math_vec_copy!( + math::Mat2, + derives(PartialEq), + no_new, + matrix { + col_type = LuaVec2 + }, + metamethods( + Eq, + Add, + Sub, + Mul(LuaMat2, f32), + Unm + ) +); */ + +// TODO +/* wrap_math_vec_copy!( + math::Mat4, + derives(PartialEq), + no_new, + matrix { + col_type = LuaVec4 + }, + metamethods( + Eq, + Add, + Sub, + Mul(LuaMat4, f32), + Unm + ) +); */ + + + + + + +// ============================================================ + + + +/// A macro that generates field getters and setters for lua wrapped types. +macro_rules! wrapped_field_getsetters { + ($builder: ident, $name: literal, $field: ident, $type: ident) => { + $builder.field_getter($name, |_, this| { + Ok($type(this.$field)) + }); + $builder.field_setter($name, |_, this, v: $type| { + this.$field = *v; + Ok(()) + }); + }; +} + +/// A macro that generates field getters and setters for types that already implement As/FromLua. +macro_rules! field_getsetters { + ($builder: ident, $name: literal, $field: ident, $type: ty) => { + $builder.field_getter($name, |_, this| { + Ok(this.$field) + }); + $builder.field_setter($name, |_, this, v: $type| { + this.$field = v; + Ok(()) + }); + }; +} + + +wrap_math_vec_copy!( + math::Quat, + derives(PartialEq), + no_new, + metamethods( + Eq, + // __mul for LuaVec3 is manually implemented below since it doesn't return Self + //Mul(LuaQuat, f32), + Add, + Sub, + Div(f32), + ), + custom_fields { + field_getsetters!(builder, "x", x, f32); + field_getsetters!(builder, "y", y, f32); + field_getsetters!(builder, "z", z, f32); + field_getsetters!(builder, "w", w, f32); + }, + custom_methods { + // manually implemented since Quat doesn't have a `new` function + builder.function("new", |_, (x, y, z, w)| { + Ok(Self(math::Quat::from_xyzw(x, y, z, w))) + }); + + builder.function("from_rotation_x", |_, (rad,)| { + let q = math::Quat::from_rotation_x(rad); + Ok(Self(q)) + }); + + builder.function("from_rotation_y", |_, (rad,)| { + let q = math::Quat::from_rotation_y(rad); + Ok(Self(q)) + }); + + builder.function("from_rotation_z", |_, (rad,)| { + let q = math::Quat::from_rotation_z(rad); + Ok(Self(q)) + }); + + builder.method("dot", |_, this, (rhs,): (Self,)| { + Ok(this.dot(rhs.0)) + }); + + builder.method("length", |_, this, ()| { + Ok(this.length()) + }); + + builder.method("length_squared", |_, this, ()| { + Ok(this.length_squared()) + }); + + builder.method_mut("normalize", |_, this, ()| { + this.0 = this.normalize(); + Ok(()) + }); + + builder.method_mut("mult_quat", |_, this, (rhs,): (Self,)| { + this.0 *= rhs.0; + Ok(()) + }); + + builder.method("mult_vec3", |_, this, (rhs,): (LuaVec3,)| { + Ok(LuaVec3(this.0 * rhs.0)) + }); + + // manually implemented here since multiplying may not return `Self`. + builder.meta_method(elua::MetaMethod::Mul, |lua, this, (val,): (elua::Value,)| { + use elua::AsLua; + + match val { + elua::Value::Userdata(ud) => { + if ud.is::()? { + let v3 = ud.as_ref::()?; + LuaVec3(this.0 * v3.0) + .as_lua(lua) + } else { + let quat = ud.as_ref::()?; + LuaQuat(this.0 * quat.0) + .as_lua(lua) + } + }, + elua::Value::Number(n) => { + LuaQuat(this.0 * (n as f32)) + .as_lua(lua) + }, + _ => { + todo!() + } + } + }); + + builder.method("lerp", |_, this, (rhs, alpha): (Self, f32)| { + Ok(Self(this.lerp(*rhs, alpha))) + }); + } +); + +wrap_math_vec_copy!( + math::Transform, + derives(PartialEq), + no_new, + metamethods(ToString, Eq), + custom_fields { + wrapped_field_getsetters!(builder, "translation", translation, LuaVec3); + wrapped_field_getsetters!(builder, "rotation", rotation, LuaQuat); + wrapped_field_getsetters!(builder, "scale", scale, LuaVec3); + }, + custom_methods { + builder.function("default", |_, ()| { + Ok(Self(math::Transform::default())) + }); + + builder.function("new", |_, (pos, rot, scale): (LuaVec3, LuaQuat, LuaVec3)| { + Ok(Self(math::Transform::new(*pos, *rot, *scale))) + }); + + builder.function("from_translation", |_, (pos,): (LuaVec3,)| { + Ok(Self(math::Transform::from_translation(*pos))) + }); + + builder.function("from_xyz", |_, (x, y, z)| { + Ok(Self(math::Transform::from_xyz(x, y, z))) + }); + + builder.method("clone", |_, this, ()| { + Ok(this.clone()) + }); + + builder.method("forward", |_, this, ()| { + Ok(LuaVec3(this.forward())) + }); + + builder.method("left", |_, this, ()| { + Ok(LuaVec3(this.left())) + }); + + builder.method("up", |_, this, ()| { + Ok(LuaVec3(this.up())) + }); + + builder.method_mut("rotate", |_, this, (quat,): (LuaQuat,)| { + this.rotate(*quat); + Ok(()) + }); + + builder.method_mut("rotate_x", |_, this, (deg,): (f32,)| { + this.rotate_x(math::Angle::Degrees(deg)); + Ok(()) + }); + + builder.method_mut("rotate_y", |_, this, (deg,): (f32,)| { + this.rotate_y(math::Angle::Degrees(deg)); + Ok(()) + }); + + builder.method_mut("rotate_z", |_, this, (deg,): (f32,)| { + this.rotate_z(math::Angle::Degrees(deg)); + Ok(()) + }); + + builder.method_mut("rotate_x_rad", |_, this, (rad,): (f32,)| { + this.rotate_x(math::Angle::Radians(rad)); + Ok(()) + }); + + builder.method_mut("rotate_y_rad", |_, this, (rad,): (f32,)| { + this.rotate_y(math::Angle::Radians(rad)); + Ok(()) + }); + + builder.method_mut("rotate_z_rad", |_, this, (rad,): (f32,)| { + this.rotate_z(math::Angle::Radians(rad)); + Ok(()) + }); + + builder.method_mut("translate", |_, this, (x, y, z): (f32, f32, f32)| { + this.translate(x, y, z); + Ok(()) + }); + + builder.method("lerp", |_, this, (rhs, alpha): (Self, f32)| { + Ok(Self(this.lerp(*rhs, alpha))) + }); + + // rotate a transform + builder.meta_method(elua::MetaMethod::Mul, |_, this, (quat,): (LuaQuat,)| { + let mut t = *this; + t.rotation *= *quat; + Ok(t) + }); + + // move a transform + builder.meta_method(elua::MetaMethod::Add, |_, this, (pos,): (LuaVec3,)| { + let mut t = *this; + t.translation += *pos; + Ok(t) + }); + } +); diff --git a/lyra-scripting/src/lua/wrappers/mod.rs b/lyra-scripting/src/lua/wrappers/mod.rs new file mode 100644 index 0000000..81b3255 --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/mod.rs @@ -0,0 +1,14 @@ +pub mod math; +pub use math::*; + +pub mod delta_time; +pub use delta_time::*; + +pub mod res_handle; +pub use res_handle::*; + +pub mod model_comp; +pub use model_comp::*; + +pub mod input_actions; +pub use input_actions::*; \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/model_comp.rs b/lyra-scripting/src/lua/wrappers/model_comp.rs new file mode 100644 index 0000000..5f32198 --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/model_comp.rs @@ -0,0 +1,69 @@ +use std::any::TypeId; +use std::{cell::Ref, sync::Arc}; + +use elua::FromLua; +use lyra_game::scene::ModelComponent; +use lyra_reflect::Reflect; +use lyra_resource::{Model, ResHandle}; + +use crate::lua::LuaWrapper; +use crate::lyra_engine; +use crate::{lua::{FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptBorrow}; + +use super::LuaResHandle; + +#[derive(Clone, Reflect)] +pub struct LuaModelComponent(pub ModelComponent); + +impl elua::Userdata for LuaModelComponent { + fn name() -> String { + "ModelComponent".to_string() + } + + fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) { + builder + .function("new", |_, model: Ref| { + let res = model.0.clone(); + let res_any = res.as_arc_any(); + match res_any.downcast::>() { + Ok(handle) => { + let res = ResHandle::::clone(&handle); + Ok(Self(ModelComponent(res))) + }, + Err(_) => { + Err(elua::Error::BadArgument { + func: Some("ModelComponent:new".to_string()), + arg_index: 1, + arg_name: Some("model".to_string()), + error: Arc::new( + elua::Error::runtime("resource handle is not a handle to a Model") + ) + }) + } + } + }) + .function(FN_NAME_INTERNAL_REFLECT_TYPE, |_, ()| { + Ok(ScriptBorrow::from_component::(None)) + }) + .method(FN_NAME_INTERNAL_REFLECT, |_, this, ()| { + Ok(ScriptBorrow::from_component(Some(this.0.clone()))) + }); + } +} + +impl<'a> FromLua<'a> for LuaModelComponent { + fn from_lua(_: &'a elua::State, val: elua::Value<'a>) -> elua::Result { + let tyname = val.type_name(); + let ud = val.as_userdata() + .ok_or(elua::Error::type_mismatch("Model", &tyname))?; + let ud = ud.as_ref::()?; + + Ok(ud.clone()) + } +} + +impl LuaWrapper for LuaModelComponent { + fn wrapped_type_id() -> std::any::TypeId { + TypeId::of::() + } +} \ No newline at end of file diff --git a/lyra-scripting/src/lua/wrappers/res_handle.rs b/lyra-scripting/src/lua/wrappers/res_handle.rs new file mode 100644 index 0000000..c17df96 --- /dev/null +++ b/lyra-scripting/src/lua/wrappers/res_handle.rs @@ -0,0 +1,77 @@ +use std::{ops::Deref, sync::Arc}; + +use elua::{AsLua, FromLua}; +use lyra_game::scene::ModelComponent; +use lyra_resource::{Model, ResHandle, ResourceStorage}; + +use crate::lua::FN_NAME_INTERNAL_AS_COMPONENT; + +use super::LuaModelComponent; + +#[derive(Clone)] +pub struct LuaResHandle(pub Arc); + +impl Deref for LuaResHandle { + type Target = dyn ResourceStorage; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +impl From> for LuaResHandle { + fn from(value: Arc) -> Self { + LuaResHandle(value) + } +} + +impl elua::Userdata for LuaResHandle { + fn name() -> String { + "Handle".to_string() + } + + fn build<'a>(builder: &mut elua::UserdataBuilder<'a, Self>) { + builder.field_getter("path", |_, this| Ok(this.path())); + builder.field_getter("version", |_, this| Ok(this.version())); + builder.field_getter("uuid", |_, this| Ok(this.uuid().to_string())); + builder.field_getter("state", |_, this| { + let name = match this.state() { + lyra_resource::ResourceState::Loading => "loading", + lyra_resource::ResourceState::Ready => "ready", + }; + + Ok(name) + }); + + builder.method("is_watched", |_, this, ()| { + Ok(this.is_watched()) + }); + + builder.method("is_loaded", |_, this, ()| { + Ok(this.is_loaded()) + }); + + builder.method(FN_NAME_INTERNAL_AS_COMPONENT, |lua, this, ()| { + let any = this.0.as_any(); + match any.downcast_ref::>() { + Some(model) => { + LuaModelComponent(ModelComponent(model.clone())).as_lua(lua) + }, + None => { + Ok(elua::Value::Nil) + } + } + }); + } +} + +impl<'a> FromLua<'a> for LuaResHandle { + fn from_lua(_: &'a elua::State, val: elua::Value<'a>) -> elua::Result { + let tyname = val.type_name(); + let ud = val.as_userdata() + .ok_or(elua::Error::type_mismatch("Handle", &tyname))?; + let handle = ud.as_ref::()?; + + Ok(handle.clone()) + } +} \ No newline at end of file diff --git a/lyra-scripting/src/script.rs b/lyra-scripting/src/script.rs index d93877b..0327834 100644 --- a/lyra-scripting/src/script.rs +++ b/lyra-scripting/src/script.rs @@ -1,8 +1,12 @@ +use std::sync::atomic::{AtomicU64, Ordering}; + use lyra_ecs::Component; use lyra_resource::ResHandle; use crate::lyra_engine; +static SCRIPT_ID_COUNTER: AtomicU64 = AtomicU64::new(0); + #[derive(Clone)] pub struct Script { res: ResHandle, @@ -15,7 +19,7 @@ impl Script { Self { res: script, name: name.to_string(), - id: 0 // TODO: make a counter + id: SCRIPT_ID_COUNTER.fetch_add(1, Ordering::AcqRel) } } diff --git a/lyra-scripting/src/world.rs b/lyra-scripting/src/world.rs index a4e1134..c2b2a1f 100644 --- a/lyra-scripting/src/world.rs +++ b/lyra-scripting/src/world.rs @@ -5,6 +5,14 @@ use lyra_ecs::{world::World, Entity}; #[derive(Clone)] pub struct ScriptEntity(pub Entity); +impl std::ops::Deref for ScriptEntity { + type Target = Entity; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[derive(Clone)] pub struct ScriptWorldPtr { pub inner: NonNull, diff --git a/shell.nix b/shell.nix index 7039c42..551dc4c 100755 --- a/shell.nix +++ b/shell.nix @@ -12,9 +12,11 @@ mkShell rec { heaptrack mold udev + lua5_4_compat ]; buildInputs = [ - udev alsa-lib vulkan-loader + udev alsa-lib libGL gcc + vulkan-loader vulkan-headers vulkan-tools xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature libxkbcommon wayland # To use the wayland feature ];