Compare commits
144 Commits
feature/sc
...
main
Author | SHA1 | Date |
---|---|---|
SeanOMik | 2b44d7f354 | |
SeanOMik | 60c139f9b2 | |
SeanOMik | 256025849e | |
SeanOMik | 8545e7e27d | |
SeanOMik | a85178eeea | |
SeanOMik | 8c1738334c | |
SeanOMik | fefcf58765 | |
SeanOMik | b0a6d30afc | |
SeanOMik | fef709d5f1 | |
SeanOMik | c91ee67961 | |
SeanOMik | c961568b96 | |
SeanOMik | 54b47c2178 | |
SeanOMik | 4449172c2b | |
SeanOMik | 4c6c6c4dd5 | |
SeanOMik | 27bc88c5a7 | |
SeanOMik | ff06bd55f3 | |
SeanOMik | d02258224a | |
SeanOMik | b45c2f4fab | |
SeanOMik | 40fa9c09da | |
SeanOMik | 87aa440691 | |
SeanOMik | cc1c482c40 | |
SeanOMik | a4ce4cb432 | |
SeanOMik | e2b554b4ef | |
SeanOMik | 6d57b40629 | |
SeanOMik | fd65f754cf | |
SeanOMik | 6c6893149a | |
SeanOMik | 1c649b2eb6 | |
SeanOMik | 7b2d2424a3 | |
SeanOMik | e8974bbd44 | |
SeanOMik | 3a80c069c9 | |
SeanOMik | 7ff67a194b | |
SeanOMik | c4aebdb25d | |
SeanOMik | 5ebbec8cf9 | |
SeanOMik | 2eeca335e2 | |
SeanOMik | 96dea5b1f9 | |
SeanOMik | 007b1047ef | |
SeanOMik | 545da71cda | |
SeanOMik | 0e71c5734f | |
SeanOMik | 6c1bff5768 | |
SeanOMik | 5f1a61ef52 | |
SeanOMik | f755a4c53b | |
SeanOMik | 9d3de88c50 | |
SeanOMik | f440f306be | |
SeanOMik | f17bf40c77 | |
SeanOMik | 5fc1a0f134 | |
SeanOMik | 33358662e2 | |
SeanOMik | 0edba69b02 | |
SeanOMik | 6182a4b9c8 | |
SeanOMik | 7576979797 | |
SeanOMik | acb58a15ff | |
SeanOMik | d5348ec172 | |
SeanOMik | 9ce79e6b29 | |
SeanOMik | 6d7932f6a5 | |
SeanOMik | 28b9604189 | |
SeanOMik | a0a2acfec0 | |
SeanOMik | ef68b2a4c5 | |
SeanOMik | 41d77c5687 | |
SeanOMik | bb21805278 | |
SeanOMik | c846d52b0d | |
SeanOMik | 7f5a1cd953 | |
SeanOMik | 9a48075f07 | |
SeanOMik | fc57777a45 | |
SeanOMik | 8c3446389c | |
SeanOMik | 64e6e4a942 | |
SeanOMik | cee6e44d61 | |
SeanOMik | b94a8e3cd3 | |
SeanOMik | bccf6287c0 | |
SeanOMik | daa6fc3d4b | |
SeanOMik | a4e80d4fec | |
SeanOMik | 4c2ed6ca80 | |
SeanOMik | 669cc7590c | |
SeanOMik | d1f1e03cbb | |
SeanOMik | 29c68abbbb | |
SeanOMik | 1b08482ef7 | |
SeanOMik | 15807a3dc1 | |
SeanOMik | f0d36e7b56 | |
SeanOMik | 6a11f7cbb7 | |
SeanOMik | db501015d0 | |
SeanOMik | 53837d469b | |
SeanOMik | e2c6b557bb | |
SeanOMik | 337ce18e8c | |
SeanOMik | 8eac563229 | |
SeanOMik | 24e1c0281e | |
SeanOMik | 246705b80b | |
SeanOMik | 3c73e1d7e2 | |
SeanOMik | 25aa902e02 | |
SeanOMik | 12c8ece418 | |
SeanOMik | 60ec62c558 | |
SeanOMik | 2daf617ba3 | |
SeanOMik | 0668be06e2 | |
SeanOMik | 347427a841 | |
SeanOMik | 4162150c5f | |
SeanOMik | 4a0d003181 | |
SeanOMik | 01a74ab9a6 | |
SeanOMik | 3dfb2520ce | |
SeanOMik | f3b5106073 | |
SeanOMik | 7ae38476fa | |
SeanOMik | 0a9e5ebcdb | |
SeanOMik | dd61e8e66c | |
SeanOMik | a3118f32e2 | |
SeanOMik | aa8d94851c | |
SeanOMik | a39d259bb4 | |
SeanOMik | a17c035c05 | |
SeanOMik | a2aac25249 | |
SeanOMik | e5018c8258 | |
SeanOMik | e00d0d71d1 | |
SeanOMik | 46cdcfdd3b | |
SeanOMik | 61efc358ce | |
SeanOMik | 763d51ae36 | |
SeanOMik | 0f11fe2e6d | |
SeanOMik | e2844a11a6 | |
SeanOMik | f0b413d9ae | |
SeanOMik | 65ff7c4f23 | |
SeanOMik | f63a7ae86a | |
SeanOMik | 834a864544 | |
SeanOMik | 014abcf7e6 | |
SeanOMik | 76ec9606ec | |
SeanOMik | 5c1ce809ff | |
SeanOMik | c73c1a7f43 | |
SeanOMik | 4ce21d4db0 | |
SeanOMik | 1818a0b48b | |
SeanOMik | cfd5cabfbb | |
SeanOMik | 22c08ba66e | |
SeanOMik | f345f065c1 | |
SeanOMik | aa3a4a17d7 | |
SeanOMik | 4a285e5866 | |
SeanOMik | 1c29e6fa72 | |
SeanOMik | de64b06e46 | |
SeanOMik | dead32dbab | |
SeanOMik | 1d7d13eb7b | |
SeanOMik | cd27c9602c | |
SeanOMik | 5331cfc2c4 | |
SeanOMik | b941fa2fe0 | |
SeanOMik | e36307eef7 | |
SeanOMik | fba925512b | |
SeanOMik | c1b5ca768f | |
SeanOMik | c3de9e77db | |
SeanOMik | 7db913d15b | |
SeanOMik | 556b603f83 | |
SeanOMik | ad40621f7c | |
SeanOMik | bcc035ab91 | |
SeanOMik | 8aae479df3 | |
SeanOMik | b76832ec05 | |
SeanOMik | ad82f61cf4 |
|
@ -0,0 +1,31 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
container: git.seanomik.net/seanomik/rust-nightly:2023-11-21-bookworm
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install system dependencies
|
||||
run: |
|
||||
apt update
|
||||
apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
cargo build
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cargo test --all
|
|
@ -1,3 +1,6 @@
|
|||
[submodule "lyra-scripting/elua"]
|
||||
path = lyra-scripting/elua
|
||||
url = ../elua.git # git@git.seanomik.net:SeanOMik/elua.git
|
||||
[submodule "wgsl-preprocessor"]
|
||||
path = wgsl-preprocessor
|
||||
url = git@git.seanomik.net:SeanOMik/wgsl-preprocessor.git
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# The run config is used for both run mode and debug mode
|
||||
|
||||
[[configs]]
|
||||
# the name of this task
|
||||
name = "Example 'simple_scene'"
|
||||
|
||||
# the type of the debugger. If not set, it can't be debugged but can still be run
|
||||
type = "lldb"
|
||||
|
||||
# the program to run
|
||||
program = "../../target/debug/simple_scene"
|
||||
|
||||
# the program arguments, e.g. args = ["arg1", "arg2"], optional
|
||||
# args = []
|
||||
|
||||
# current working directory, optional
|
||||
cwd = "${workspace}/examples/simple_scene"
|
||||
|
||||
# environment variables, optional
|
||||
# [configs.env]
|
||||
# VAR1 = "VAL1"
|
||||
# VAR2 = "VAL2"
|
||||
|
||||
# task to run before the run/debug session is started, optional
|
||||
[configs.prelaunch]
|
||||
program = "cargo"
|
||||
args = [
|
||||
"build",
|
||||
]
|
|
@ -22,6 +22,42 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}/examples/testbed"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug lyra shadows",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--manifest-path", "${workspaceFolder}/examples/shadows/Cargo.toml"
|
||||
//"--bin=shadows",
|
||||
],
|
||||
"filter": {
|
||||
"name": "shadows",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/examples/shadows"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug example simple_scene",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--manifest-path", "${workspaceFolder}/examples/simple_scene/Cargo.toml"
|
||||
//"--bin=testbed",
|
||||
],
|
||||
"filter": {
|
||||
"name": "simple_scene",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/examples/simple_scene"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
@ -83,6 +119,28 @@
|
|||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/lyra-ecs"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'lyra-scene'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--lib",
|
||||
"--package=lyra-scene",
|
||||
//"command::tests::deferred_commands",
|
||||
//"--",
|
||||
//"--exact --nocapture"
|
||||
],
|
||||
"filter": {
|
||||
"name": "lyra-scene",
|
||||
"kind": "lib"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/lyra-scene"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
variables:
|
||||
- &rust_image 'git.seanomik.net/seanomik/rust-nightly:2023-11-21-bookworm'
|
||||
|
||||
when:
|
||||
event: [push, manual, pull_request]
|
||||
branch: main
|
||||
|
||||
steps:
|
||||
Build - Debug:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo build
|
||||
|
||||
Test - Debug:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo test --all
|
|
@ -1,20 +0,0 @@
|
|||
variables:
|
||||
- &rust_image 'git.seanomik.net/seanomik/rust-nightly:2023-11-21-bookworm'
|
||||
|
||||
when:
|
||||
event: [release, pull_request, manual]
|
||||
|
||||
steps:
|
||||
Build - Release:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo build --release
|
||||
|
||||
Test - Release:
|
||||
image: *rust_image
|
||||
commands:
|
||||
- apt update
|
||||
- apt install libudev-dev lua5.4 liblua5.4-dev -y
|
||||
- cargo test --all --release
|
|
@ -56,6 +56,33 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923"
|
||||
dependencies = [
|
||||
"as-slice",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned-array"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c92d086290f52938013f6242ac62bf7d401fab8ad36798a609faa65c3fd2c"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.16"
|
||||
|
@ -97,9 +124,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.79"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
|
@ -113,6 +140,15 @@ version = "0.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "as-slice"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ash"
|
||||
version = "0.37.3+1.3.251"
|
||||
|
@ -330,12 +366,24 @@ version = "1.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "atomic_refcell"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "az"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.69"
|
||||
|
@ -369,6 +417,12 @@ version = "1.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bind_match"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "171f0236f66c7be99f32060539c2bade94033ded356ecf4c9dc9b1e6198326cd"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.3"
|
||||
|
@ -708,12 +762,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.18"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
|
@ -768,6 +819,12 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "divrem"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69dde51e8fef5e12c1d65e0929b03d66e4c0c18282bc30ed2ca050ad6f44dd82"
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
|
@ -777,6 +834,12 @@ dependencies = [
|
|||
"libloading 0.8.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.0"
|
||||
|
@ -789,6 +852,12 @@ version = "1.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "elapsed"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f4e5af126dafd0741c2ad62d47f68b28602550102e5f0dd45c8a97fc8b49c29"
|
||||
|
||||
[[package]]
|
||||
name = "elua"
|
||||
version = "0.1.0"
|
||||
|
@ -890,6 +959,18 @@ dependencies = [
|
|||
"zune-inflate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast_poisson"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2472baa9796d2ee497bd61690e3093a26935390d8ce0dd0ddc2db9b47a65898f"
|
||||
dependencies = [
|
||||
"kiddo",
|
||||
"rand 0.8.5",
|
||||
"rand_distr",
|
||||
"rand_xoshiro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
|
@ -935,6 +1016,37 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed"
|
||||
version = "1.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fc715d38bea7b5bf487fcd79bcf8c209f0b58014f3018a7a19c2b855f472048"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bytemuck",
|
||||
"half",
|
||||
"num-traits",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed-timestep-rotating-model"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"fps_counter",
|
||||
"lyra-engine",
|
||||
"rand 0.8.5",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.28"
|
||||
|
@ -986,9 +1098,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "fps_counter"
|
||||
version = "2.0.0"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3aaba7ff514ee9d802b562927f80b1e94e93d8e74c31b134c9c3762dabf1a36b"
|
||||
checksum = "ff23a4d90ba4b859f370ee3c12ca3b1ca80d8ee144b279e135f6852cdadd6dd6"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
|
@ -1090,6 +1205,19 @@ dependencies = [
|
|||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"log",
|
||||
"rustversion",
|
||||
"windows 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
|
@ -1631,6 +1759,26 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kiddo"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1c5ea778d68eacd5c33f29537ba0b7b6c2595e74ee013a69cedc20ab4d3177"
|
||||
dependencies = [
|
||||
"aligned",
|
||||
"aligned-array",
|
||||
"az",
|
||||
"divrem",
|
||||
"doc-comment",
|
||||
"elapsed",
|
||||
"fixed",
|
||||
"log",
|
||||
"min-max-heap",
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.8"
|
||||
|
@ -1698,6 +1846,12 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.0.2"
|
||||
|
@ -1750,16 +1904,43 @@ dependencies = [
|
|||
"value-bag",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e045d70ddfbc984eacfa964ded019534e8f6cbf36f6410aee0ed5cefa5a9175"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generator",
|
||||
"scoped-tls",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lua-scripting"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"fps_counter",
|
||||
"lyra-engine",
|
||||
"rand 0.8.5",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyra-ecs"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atomic_refcell",
|
||||
"lyra-ecs-derive",
|
||||
"lyra-math",
|
||||
"paste",
|
||||
"rand 0.8.5",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1786,29 +1967,48 @@ dependencies = [
|
|||
"anyhow",
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"bind_match",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"fast_poisson",
|
||||
"gilrs-core",
|
||||
"glam",
|
||||
"image",
|
||||
"instant",
|
||||
"itertools 0.11.0",
|
||||
"lyra-ecs",
|
||||
"lyra-game-derive",
|
||||
"lyra-math",
|
||||
"lyra-reflect",
|
||||
"lyra-resource",
|
||||
"lyra-scene",
|
||||
"petgraph",
|
||||
"quote",
|
||||
"round_mult",
|
||||
"rustc-hash",
|
||||
"syn 2.0.51",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-log 0.1.4",
|
||||
"tracing-subscriber",
|
||||
"tracing-tracy",
|
||||
"unique",
|
||||
"uuid",
|
||||
"wgpu",
|
||||
"wgsl_preprocessor",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyra-game-derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyra-math"
|
||||
version = "0.1.0"
|
||||
|
@ -1839,27 +2039,44 @@ name = "lyra-resource"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"base64 0.21.5",
|
||||
"crossbeam",
|
||||
"glam",
|
||||
"gltf",
|
||||
"image",
|
||||
"infer",
|
||||
"instant",
|
||||
"lyra-ecs",
|
||||
"lyra-math",
|
||||
"lyra-reflect",
|
||||
"lyra-scene",
|
||||
"mime",
|
||||
"notify",
|
||||
"notify-debouncer-full",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyra-scene"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lyra-ecs",
|
||||
"lyra-math",
|
||||
"lyra-reflect",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyra-scripting"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"atomic_refcell",
|
||||
"elua",
|
||||
"itertools 0.12.0",
|
||||
"lyra-ecs",
|
||||
|
@ -1867,6 +2084,7 @@ dependencies = [
|
|||
"lyra-reflect",
|
||||
"lyra-resource",
|
||||
"lyra-scripting-derive",
|
||||
"paste",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
@ -1876,6 +2094,7 @@ dependencies = [
|
|||
name = "lyra-scripting-derive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"paste",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.51",
|
||||
|
@ -1899,6 +2118,27 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "many-lights"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"fps_counter",
|
||||
"lyra-engine",
|
||||
"rand 0.8.5",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.1"
|
||||
|
@ -1943,6 +2183,12 @@ version = "0.3.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "min-max-heap"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2687e6cf9c00f48e9284cf9fd15f2ef341d03cc7743abf9df4c5f07fdee50b18"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
|
@ -2150,6 +2396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2396,6 +2643,61 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.51",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.7.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap 2.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.13"
|
||||
|
@ -2581,6 +2883,25 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_distr"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xoshiro"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "range-alloc"
|
||||
version = "0.1.3"
|
||||
|
@ -2640,6 +2961,50 @@ dependencies = [
|
|||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.6",
|
||||
"regex-syntax 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
|
@ -2695,6 +3060,15 @@ dependencies = [
|
|||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "round_mult"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74bc7d5286c4d36f09aa6ae93f76acf6aa068cd62bc02970a9deb24763655dee"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
|
@ -2707,6 +3081,15 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.27"
|
||||
|
@ -2743,6 +3126,12 @@ dependencies = [
|
|||
"base64 0.21.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.16"
|
||||
|
@ -2815,6 +3204,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.194"
|
||||
|
@ -2880,6 +3275,16 @@ dependencies = [
|
|||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shadows"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"lyra-engine",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
|
@ -2904,6 +3309,16 @@ version = "0.3.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "simple_scene"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"lyra-engine",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
@ -2986,6 +3401,12 @@ dependencies = [
|
|||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
|
@ -3091,26 +3512,24 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"fps_counter",
|
||||
"lyra-engine",
|
||||
"lyra-scripting",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.56"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.56"
|
||||
version = "1.0.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -3342,14 +3761,49 @@ version = "0.3.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-tracy"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6024d04f84a69fd0d1dc1eee3a2b070bd246530a0582f9982ae487cb6c703614"
|
||||
dependencies = [
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
"tracy-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracy-client"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59fb931a64ff88984f86d3e9bcd1ae8843aa7fe44dd0f8097527bc172351741d"
|
||||
dependencies = [
|
||||
"loom",
|
||||
"once_cell",
|
||||
"tracy-client-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracy-client-sys"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d104d610dfa9dd154535102cc9c6164ae1fa37842bc2d9e83f9ac82b0ae0882"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
|
@ -3368,6 +3822,12 @@ version = "1.17.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.15"
|
||||
|
@ -3401,6 +3861,12 @@ version = "0.2.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "unique"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d360722e1f3884f5b14d332185f02ff111f771f0c76a313268fe6af1409aba96"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.0"
|
||||
|
@ -3744,6 +4210,18 @@ dependencies = [
|
|||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgsl_preprocessor"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"regex",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "0.5.1"
|
||||
|
@ -3790,6 +4268,15 @@ dependencies = [
|
|||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.52.0"
|
||||
|
|
20
Cargo.toml
|
@ -5,17 +5,33 @@ edition = "2021"
|
|||
|
||||
[workspace]
|
||||
members = [
|
||||
"examples/testbed",
|
||||
"lyra-resource",
|
||||
"lyra-ecs",
|
||||
"lyra-reflect",
|
||||
"lyra-scripting",
|
||||
"lyra-game", "lyra-math"]
|
||||
"lyra-game",
|
||||
"lyra-math",
|
||||
"lyra-scene",
|
||||
|
||||
"examples/testbed",
|
||||
"examples/many-lights",
|
||||
"examples/fixed-timestep-rotating-model",
|
||||
"examples/lua-scripting",
|
||||
"examples/simple_scene",
|
||||
"examples/shadows",
|
||||
]
|
||||
|
||||
[features]
|
||||
scripting = ["dep:lyra-scripting"]
|
||||
lua_scripting = ["scripting", "lyra-scripting/lua"]
|
||||
tracy = ["lyra-game/tracy"]
|
||||
|
||||
[dependencies]
|
||||
lyra-game = { path = "lyra-game" }
|
||||
lyra-scripting = { path = "lyra-scripting", optional = true }
|
||||
|
||||
#[profile.dev]
|
||||
#opt-level = 1
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 457 KiB After Width: | Height: | Size: 457 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
@ -0,0 +1,2 @@
|
|||
Sponza*
|
||||
Textures
|
|
@ -0,0 +1 @@
|
|||
To keep the size of this repository down, the Sponza scene is omitted from this repo. The files were downloaded from https://github.com/toji/sponza-optimized
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
@ -0,0 +1,142 @@
|
|||
{
|
||||
"asset":{
|
||||
"generator":"Khronos glTF Blender I/O v4.1.63",
|
||||
"version":"2.0"
|
||||
},
|
||||
"scene":0,
|
||||
"scenes":[
|
||||
{
|
||||
"name":"Scene",
|
||||
"nodes":[
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes":[
|
||||
{
|
||||
"mesh":0,
|
||||
"name":"Cube",
|
||||
"scale":[
|
||||
10,
|
||||
0.25,
|
||||
10
|
||||
]
|
||||
}
|
||||
],
|
||||
"materials":[
|
||||
{
|
||||
"doubleSided":true,
|
||||
"name":"Material.001",
|
||||
"pbrMetallicRoughness":{
|
||||
"baseColorTexture":{
|
||||
"index":0
|
||||
},
|
||||
"metallicFactor":0,
|
||||
"roughnessFactor":0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes":[
|
||||
{
|
||||
"name":"Cube.001",
|
||||
"primitives":[
|
||||
{
|
||||
"attributes":{
|
||||
"POSITION":0,
|
||||
"NORMAL":1,
|
||||
"TEXCOORD_0":2
|
||||
},
|
||||
"indices":3,
|
||||
"material":0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"textures":[
|
||||
{
|
||||
"sampler":0,
|
||||
"source":0
|
||||
}
|
||||
],
|
||||
"images":[
|
||||
{
|
||||
"mimeType":"image/jpeg",
|
||||
"name":"wood1",
|
||||
"uri":"wood1.jpg"
|
||||
}
|
||||
],
|
||||
"accessors":[
|
||||
{
|
||||
"bufferView":0,
|
||||
"componentType":5126,
|
||||
"count":24,
|
||||
"max":[
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"min":[
|
||||
-1,
|
||||
-1,
|
||||
-1
|
||||
],
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":1,
|
||||
"componentType":5126,
|
||||
"count":24,
|
||||
"type":"VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView":2,
|
||||
"componentType":5126,
|
||||
"count":24,
|
||||
"type":"VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView":3,
|
||||
"componentType":5123,
|
||||
"count":36,
|
||||
"type":"SCALAR"
|
||||
}
|
||||
],
|
||||
"bufferViews":[
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":288,
|
||||
"byteOffset":0,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":288,
|
||||
"byteOffset":288,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":192,
|
||||
"byteOffset":576,
|
||||
"target":34962
|
||||
},
|
||||
{
|
||||
"buffer":0,
|
||||
"byteLength":72,
|
||||
"byteOffset":768,
|
||||
"target":34963
|
||||
}
|
||||
],
|
||||
"samplers":[
|
||||
{
|
||||
"magFilter":9729,
|
||||
"minFilter":9987
|
||||
}
|
||||
],
|
||||
"buffers":[
|
||||
{
|
||||
"byteLength":840,
|
||||
"uri":"model.bin"
|
||||
}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 47 KiB |
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "fixed-timestep-rotating-model"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lyra-engine = { path = "../../", features = ["tracy"] }
|
||||
anyhow = "1.0.75"
|
||||
async-std = "1.12.0"
|
||||
tracing = "0.1.37"
|
||||
rand = "0.8.5"
|
||||
fps_counter = "3.0.0"
|
|
@ -0,0 +1,241 @@
|
|||
use std::ptr::NonNull;
|
||||
|
||||
use lyra_engine::{
|
||||
assets::{gltf::Gltf, ResourceManager},
|
||||
ecs::{
|
||||
query::{Res, ResMut, View},
|
||||
system::{BatchedSystem, Criteria, CriteriaSchedule, IntoSystem},
|
||||
World,
|
||||
},
|
||||
game::Game,
|
||||
input::{
|
||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||
},
|
||||
math::{self, Transform, Vec3},
|
||||
render::light::directional::DirectionalLight,
|
||||
scene::{
|
||||
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
|
||||
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
|
||||
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
|
||||
},
|
||||
DeltaTime,
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
let action_handler_plugin = |game: &mut Game| {
|
||||
let action_handler = ActionHandler::builder()
|
||||
.add_layout(LayoutId::from(0))
|
||||
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
|
||||
.add_action("Debug", Action::new(ActionKind::Button))
|
||||
.add_mapping(
|
||||
ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
|
||||
.bind(
|
||||
ACTLBL_MOVE_FORWARD_BACKWARD,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_ROLL,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
"Debug",
|
||||
&[ActionSource::Keyboard(KeyCode::B).into_binding()],
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
let world = game.world_mut();
|
||||
world.add_resource(action_handler);
|
||||
game.with_plugin(InputActionPlugin);
|
||||
};
|
||||
|
||||
Game::initialize()
|
||||
.await
|
||||
.with_plugin(lyra_engine::DefaultPlugins)
|
||||
.with_plugin(setup_scene_plugin)
|
||||
.with_plugin(action_handler_plugin)
|
||||
//.with_plugin(camera_debug_plugin)
|
||||
.with_plugin(FreeFlyCameraPlugin)
|
||||
.run()
|
||||
.await;
|
||||
}
|
||||
|
||||
fn setup_scene_plugin(game: &mut Game) {
|
||||
let world = game.world_mut();
|
||||
let resman = world.get_resource_mut::<ResourceManager>();
|
||||
let camera_gltf = resman
|
||||
.request::<Gltf>("../assets/AntiqueCamera.glb")
|
||||
.unwrap();
|
||||
|
||||
camera_gltf.wait_recurse_dependencies_load();
|
||||
let camera_mesh = &camera_gltf.data_ref().unwrap().scenes[0];
|
||||
drop(resman);
|
||||
|
||||
world.spawn((
|
||||
camera_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, -5.0, 0.0),
|
||||
));
|
||||
|
||||
{
|
||||
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
|
||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||
light_tran.rotate_x(math::Angle::Degrees(-45.0));
|
||||
light_tran.rotate_y(math::Angle::Degrees(25.0));
|
||||
world.spawn((
|
||||
DirectionalLight {
|
||||
enabled: true,
|
||||
color: Vec3::ONE,
|
||||
intensity: 0.15, //..Default::default()
|
||||
},
|
||||
light_tran,
|
||||
));
|
||||
}
|
||||
|
||||
let mut camera = CameraComponent::new_3d();
|
||||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 1.5);
|
||||
world.spawn((camera, FreeFlyCamera::default()));
|
||||
|
||||
let fps_counter = |mut counter: ResMut<fps_counter::FPSCounter>,
|
||||
delta: Res<DeltaTime>| -> anyhow::Result<()> {
|
||||
let tick = counter.tick();
|
||||
|
||||
info!("FPS: {}, frame time: {}", tick, **delta);
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
world.add_resource(fps_counter::FPSCounter::new());
|
||||
|
||||
let rotate_system = |dt: Res<DeltaTime>, view: View<&mut Transform>| -> anyhow::Result<()> {
|
||||
const SPEED: f32 = 4.0;
|
||||
let dt = **dt;
|
||||
|
||||
for mut transform in view.iter() {
|
||||
info!("rotation: {:?}", transform.rotation);
|
||||
transform.rotate_y(math::Angle::Degrees(SPEED * dt));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let mut sys = BatchedSystem::new();
|
||||
sys.with_criteria(FixedTimestep::new(60));
|
||||
sys.with_system(rotate_system.into_system());
|
||||
sys.with_system(fps_counter.into_system());
|
||||
|
||||
game.with_system("fixed_timestep", sys, &[]);
|
||||
}
|
||||
|
||||
struct FixedTimestep {
|
||||
max_tps: u32,
|
||||
fixed_time: f32,
|
||||
accumulator: f32,
|
||||
old_dt: Option<DeltaTime>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl FixedTimestep {
|
||||
pub fn new(max_tps: u32) -> Self {
|
||||
Self {
|
||||
max_tps,
|
||||
fixed_time: Self::calc_fixed_time(max_tps),
|
||||
accumulator: 0.0,
|
||||
old_dt: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_fixed_time(max_tps: u32) -> f32 {
|
||||
1.0 / max_tps as f32
|
||||
}
|
||||
|
||||
fn set_tps(&mut self, tps: u32) {
|
||||
self.max_tps = tps;
|
||||
self.fixed_time = Self::calc_fixed_time(tps);
|
||||
}
|
||||
|
||||
fn tps(&self) -> u32 {
|
||||
self.max_tps
|
||||
}
|
||||
|
||||
fn fixed_time(&self) -> f32 {
|
||||
self.fixed_time
|
||||
}
|
||||
}
|
||||
|
||||
impl Criteria for FixedTimestep {
|
||||
fn can_run(&mut self, mut world: NonNull<World>, check_count: u32) -> CriteriaSchedule {
|
||||
let world = unsafe { world.as_mut() };
|
||||
if check_count == 0 {
|
||||
let delta_time = world.get_resource::<DeltaTime>();
|
||||
self.accumulator += **delta_time;
|
||||
}
|
||||
|
||||
if self.accumulator >= self.fixed_time {
|
||||
self.accumulator -= self.fixed_time;
|
||||
return CriteriaSchedule::YesAndLoop;
|
||||
}
|
||||
|
||||
CriteriaSchedule::No
|
||||
}
|
||||
|
||||
fn modify_world(&mut self, mut world: NonNull<World>) {
|
||||
let world = unsafe { world.as_mut() };
|
||||
self.old_dt = world.try_get_resource().map(|r| *r);
|
||||
|
||||
world.add_resource(DeltaTime::from(self.fixed_time));
|
||||
}
|
||||
|
||||
fn undo_world_modifications(&mut self, mut world: NonNull<World>) {
|
||||
let world = unsafe { world.as_mut() };
|
||||
world.add_resource(
|
||||
self.old_dt
|
||||
.expect("DeltaTime resource was somehow never got from the world"),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "lua-scripting"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lyra-engine = { path = "../../", features = ["tracy", "lua_scripting"] }
|
||||
anyhow = "1.0.75"
|
||||
async-std = "1.12.0"
|
||||
tracing = "0.1.37"
|
||||
rand = "0.8.5"
|
||||
fps_counter = "3.0.0"
|
|
@ -0,0 +1,59 @@
|
|||
---Return the userdata's name from its metatable
|
||||
---@param val userdata
|
||||
---@return string
|
||||
function udname(val)
|
||||
return getmetatable(val).__name
|
||||
end
|
||||
|
||||
function on_init()
|
||||
local cube = world:request_res("../assets/cube-texture-embedded.gltf")
|
||||
print("Loaded textured cube (" .. udname(cube) .. ")")
|
||||
|
||||
cube:wait_until_loaded()
|
||||
local scenes = cube:scenes()
|
||||
local cube_scene = scenes[1]
|
||||
|
||||
local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
|
||||
|
||||
local e = world:spawn(pos, cube_scene)
|
||||
print("spawned entity " .. tostring(e))
|
||||
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) ]]
|
||||
|
||||
---@type number
|
||||
local dt = world:resource(DeltaTime)
|
||||
|
||||
world:view(function (t)
|
||||
t:translate(0, 0.15 * dt, 0)
|
||||
return t
|
||||
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 ]]
|
|
@ -0,0 +1,151 @@
|
|||
use lyra_engine::{
|
||||
assets::{gltf::Gltf, ResourceManager},
|
||||
game::Game,
|
||||
input::{
|
||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||
},
|
||||
lua::{LuaScript, LuaScriptingPlugin},
|
||||
math::{self, Transform, Vec3},
|
||||
render::light::directional::DirectionalLight,
|
||||
scene::{
|
||||
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
|
||||
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
|
||||
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
|
||||
},
|
||||
Script, ScriptList,
|
||||
};
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
let action_handler_plugin = |game: &mut Game| {
|
||||
let action_handler = ActionHandler::builder()
|
||||
.add_layout(LayoutId::from(0))
|
||||
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
|
||||
.add_action("Debug", Action::new(ActionKind::Button))
|
||||
.add_mapping(
|
||||
ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
|
||||
.bind(
|
||||
ACTLBL_MOVE_FORWARD_BACKWARD,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_ROLL,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
"Debug",
|
||||
&[ActionSource::Keyboard(KeyCode::B).into_binding()],
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
let world = game.world_mut();
|
||||
world.add_resource(action_handler);
|
||||
game.with_plugin(InputActionPlugin);
|
||||
};
|
||||
|
||||
Game::initialize()
|
||||
.await
|
||||
.with_plugin(lyra_engine::DefaultPlugins)
|
||||
.with_plugin(setup_scene_plugin)
|
||||
.with_plugin(action_handler_plugin)
|
||||
.with_plugin(setup_script_plugin)
|
||||
//.with_plugin(camera_debug_plugin)
|
||||
.with_plugin(FreeFlyCameraPlugin)
|
||||
.run()
|
||||
.await;
|
||||
}
|
||||
|
||||
fn setup_scene_plugin(game: &mut Game) {
|
||||
let world = game.world_mut();
|
||||
let resman = world.get_resource_mut::<ResourceManager>();
|
||||
let camera_gltf = resman
|
||||
.request::<Gltf>("../assets/AntiqueCamera.glb")
|
||||
.unwrap();
|
||||
|
||||
camera_gltf.wait_recurse_dependencies_load();
|
||||
let camera_mesh = &camera_gltf.data_ref().unwrap().scenes[0];
|
||||
drop(resman);
|
||||
|
||||
world.spawn((
|
||||
camera_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, -5.0, -2.0),
|
||||
));
|
||||
|
||||
{
|
||||
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
|
||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||
light_tran.rotate_x(math::Angle::Degrees(-45.0));
|
||||
light_tran.rotate_y(math::Angle::Degrees(25.0));
|
||||
world.spawn((
|
||||
DirectionalLight {
|
||||
enabled: true,
|
||||
color: Vec3::ONE,
|
||||
intensity: 0.15, //..Default::default()
|
||||
},
|
||||
light_tran,
|
||||
));
|
||||
}
|
||||
|
||||
let mut camera = CameraComponent::new_3d();
|
||||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
||||
world.spawn((camera, FreeFlyCamera::default()));
|
||||
}
|
||||
|
||||
fn setup_script_plugin(game: &mut Game) {
|
||||
game.with_plugin(LuaScriptingPlugin);
|
||||
|
||||
let world = game.world_mut();
|
||||
let res_man = world.get_resource_mut::<ResourceManager>();
|
||||
let script = res_man.request::<LuaScript>("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,));
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "many-lights"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lyra-engine = { path = "../../", features = ["tracy"] }
|
||||
anyhow = "1.0.75"
|
||||
async-std = "1.12.0"
|
||||
tracing = "0.1.37"
|
||||
rand = "0.8.5"
|
||||
fps_counter = "3.0.0"
|
|
@ -0,0 +1,189 @@
|
|||
use lyra_engine::{assets::{gltf::Gltf, ResourceManager}, ecs::query::{Res, ResMut, View}, game::Game, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Quat, Transform, Vec3}, render::light::{directional::DirectionalLight, PointLight}, scene::{CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN}, DeltaTime};
|
||||
use rand::Rng;
|
||||
use tracing::info;
|
||||
|
||||
const MAX_POINT_LIGHT_RANGE: f32 = 1.0;
|
||||
const MIN_POINT_LIGHT_RANGE: f32 = 0.5;
|
||||
const POINT_LIGHT_MAX_INTENSITY: f32 = 1.0;
|
||||
const POINT_LIGHT_MIN_INTENSITY: f32 = 0.3;
|
||||
const POINT_LIGHT_CUBE_SCALE: f32 = 0.2;
|
||||
const POINT_LIGHT_NUM: u32 = 500;
|
||||
|
||||
const POINT_LIGHT_MAX_X: f32 = 9.0;
|
||||
const POINT_LIGHT_MIN_X: f32 = -9.0;
|
||||
|
||||
const POINT_LIGHT_MAX_Y: f32 = 3.0;
|
||||
const POINT_LIGHT_MIN_Y: f32 = 0.5;
|
||||
|
||||
const POINT_LIGHT_MAX_Z: f32 = 4.0;
|
||||
const POINT_LIGHT_MIN_Z: f32 = -5.0;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
|
||||
let action_handler_plugin = |game: &mut Game| {
|
||||
let action_handler = ActionHandler::builder()
|
||||
.add_layout(LayoutId::from(0))
|
||||
|
||||
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
|
||||
.add_action("Debug", Action::new(ActionKind::Button))
|
||||
|
||||
.add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
|
||||
.bind(ACTLBL_MOVE_FORWARD_BACKWARD, &[
|
||||
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0)
|
||||
])
|
||||
.bind(ACTLBL_MOVE_LEFT_RIGHT, &[
|
||||
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0)
|
||||
])
|
||||
.bind(ACTLBL_MOVE_UP_DOWN, &[
|
||||
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0)
|
||||
])
|
||||
.bind(ACTLBL_LOOK_LEFT_RIGHT, &[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
|
||||
])
|
||||
.bind(ACTLBL_LOOK_UP_DOWN, &[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
|
||||
])
|
||||
.bind(ACTLBL_LOOK_ROLL, &[
|
||||
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
|
||||
])
|
||||
.bind("Debug", &[
|
||||
ActionSource::Keyboard(KeyCode::B).into_binding(),
|
||||
])
|
||||
.finish()
|
||||
).finish();
|
||||
|
||||
let world = game.world_mut();
|
||||
world.add_resource(action_handler);
|
||||
game.with_plugin(InputActionPlugin);
|
||||
};
|
||||
|
||||
Game::initialize().await
|
||||
.with_plugin(lyra_engine::DefaultPlugins)
|
||||
.with_plugin(setup_scene_plugin)
|
||||
.with_plugin(action_handler_plugin)
|
||||
.with_plugin(camera_debug_plugin)
|
||||
.with_plugin(FreeFlyCameraPlugin)
|
||||
.run().await;
|
||||
}
|
||||
|
||||
fn setup_scene_plugin(game: &mut Game) {
|
||||
let fps_counter = |mut counter: ResMut<fps_counter::FPSCounter>, delta: Res<DeltaTime>| -> anyhow::Result<()> {
|
||||
let tick = counter.tick();
|
||||
|
||||
info!("FPS: {}, frame time: {}", tick, **delta);
|
||||
|
||||
Ok(())
|
||||
};
|
||||
game.with_system("fps_counter", fps_counter, &[]);
|
||||
|
||||
let world = game.world_mut();
|
||||
world.add_resource(fps_counter::FPSCounter::new());
|
||||
|
||||
let resman = world.get_resource_mut::<ResourceManager>();
|
||||
let cube_gltf = resman.request::<Gltf>("../assets/texture-sep/texture-sep.gltf").unwrap();
|
||||
|
||||
cube_gltf.wait_recurse_dependencies_load();
|
||||
let cube_mesh = &cube_gltf.data_ref()
|
||||
.unwrap().meshes[0];
|
||||
|
||||
let sponza_model = resman.request::<Gltf>("../assets/sponza/Sponza.gltf").unwrap();
|
||||
drop(resman);
|
||||
|
||||
sponza_model.wait_recurse_dependencies_load();
|
||||
let sponza_scene = &sponza_model.data_ref()
|
||||
.unwrap().scenes[0];
|
||||
|
||||
world.spawn((
|
||||
sponza_scene.clone(),
|
||||
Transform::from_xyz(0.0, 0.0, 0.0),
|
||||
));
|
||||
|
||||
{
|
||||
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
|
||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||
light_tran.rotate_x(math::Angle::Degrees(-45.0));
|
||||
light_tran.rotate_y(math::Angle::Degrees(25.0));
|
||||
world.spawn((
|
||||
DirectionalLight {
|
||||
enabled: true,
|
||||
color: Vec3::ONE,
|
||||
intensity: 0.15
|
||||
//..Default::default()
|
||||
},
|
||||
light_tran,
|
||||
));
|
||||
}
|
||||
|
||||
let x_range = POINT_LIGHT_MIN_X..POINT_LIGHT_MAX_X;
|
||||
let y_range = POINT_LIGHT_MIN_Y..POINT_LIGHT_MAX_Y;
|
||||
let z_range = POINT_LIGHT_MIN_Z..POINT_LIGHT_MAX_Z;
|
||||
|
||||
let mut rand = rand::thread_rng();
|
||||
let mut rand_vec3 = || -> Vec3 {
|
||||
let x = rand.gen_range(x_range.clone());
|
||||
let y = rand.gen_range(y_range.clone());
|
||||
let z = rand.gen_range(z_range.clone());
|
||||
|
||||
Vec3::new(x, y, z)
|
||||
};
|
||||
|
||||
let mut rand = rand::thread_rng();
|
||||
for _ in 0..POINT_LIGHT_NUM {
|
||||
let range = rand.gen_range(MIN_POINT_LIGHT_RANGE..MAX_POINT_LIGHT_RANGE);
|
||||
let intensity = rand.gen_range(POINT_LIGHT_MIN_INTENSITY..POINT_LIGHT_MAX_INTENSITY);
|
||||
let color = rand_vec3().normalize();
|
||||
|
||||
world.spawn((
|
||||
PointLight {
|
||||
enabled: true,
|
||||
color,
|
||||
intensity,
|
||||
range,
|
||||
..Default::default()
|
||||
},
|
||||
Transform::new(
|
||||
rand_vec3(),
|
||||
Quat::IDENTITY,
|
||||
Vec3::new(POINT_LIGHT_CUBE_SCALE, POINT_LIGHT_CUBE_SCALE, POINT_LIGHT_CUBE_SCALE),
|
||||
),
|
||||
cube_mesh.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut camera = CameraComponent::new_3d();
|
||||
// these values were taken by manually positioning the camera in the scene.
|
||||
camera.transform = Transform::new(
|
||||
Vec3::new(-10.0, 0.94, -0.28),
|
||||
Quat::from_xyzw(0.03375484, -0.7116095, 0.0342693, 0.70092666),
|
||||
Vec3::ONE
|
||||
);
|
||||
world.spawn(( camera, FreeFlyCamera::default() ));
|
||||
}
|
||||
|
||||
fn camera_debug_plugin(game: &mut Game) {
|
||||
let sys = |handler: Res<ActionHandler>, view: View<&mut CameraComponent>| -> anyhow::Result<()> {
|
||||
if handler.was_action_just_pressed("Debug") {
|
||||
for mut cam in view.into_iter() {
|
||||
cam.debug = !cam.debug;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
game.with_system("camera_debug_trigger", sys, &[]);
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "shadows"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lyra-engine = { path = "../../", features = ["tracy"] }
|
||||
anyhow = "1.0.75"
|
||||
async-std = "1.12.0"
|
||||
tracing = "0.1.37"
|
|
@ -0,0 +1,59 @@
|
|||
---Return the userdata's name from its metatable
|
||||
---@param val userdata
|
||||
---@return string
|
||||
function udname(val)
|
||||
return getmetatable(val).__name
|
||||
end
|
||||
|
||||
function on_init()
|
||||
local cube = world:request_res("../assets/cube-texture-embedded.gltf")
|
||||
print("Loaded textured cube (" .. udname(cube) .. ")")
|
||||
|
||||
cube:wait_until_loaded()
|
||||
local scenes = cube:scenes()
|
||||
local cube_scene = scenes[1]
|
||||
|
||||
local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
|
||||
|
||||
local e = world:spawn(pos, cube_scene)
|
||||
print("spawned entity " .. tostring(e))
|
||||
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) ]]
|
||||
|
||||
---@type number
|
||||
local dt = world:resource(DeltaTime)
|
||||
|
||||
world:view(function (t)
|
||||
t:translate(0, 0.15 * dt, 0)
|
||||
return t
|
||||
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 ]]
|
|
@ -0,0 +1,249 @@
|
|||
use lyra_engine::{
|
||||
assets::{gltf::Gltf, ResourceManager},
|
||||
game::Game,
|
||||
input::{
|
||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||
},
|
||||
math::{self, Quat, Transform, Vec3},
|
||||
render::{
|
||||
graph::{ShadowCasterSettings, ShadowFilteringMode},
|
||||
light::{directional::DirectionalLight, PointLight, SpotLight},
|
||||
},
|
||||
scene::{
|
||||
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
|
||||
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
|
||||
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
|
||||
},
|
||||
};
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
let action_handler_plugin = |game: &mut Game| {
|
||||
let action_handler = ActionHandler::builder()
|
||||
.add_layout(LayoutId::from(0))
|
||||
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
|
||||
.add_action("Debug", Action::new(ActionKind::Button))
|
||||
.add_mapping(
|
||||
ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
|
||||
.bind(
|
||||
ACTLBL_MOVE_FORWARD_BACKWARD,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_ROLL,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
"Debug",
|
||||
&[ActionSource::Keyboard(KeyCode::B).into_binding()],
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
let world = game.world_mut();
|
||||
world.add_resource(action_handler);
|
||||
game.with_plugin(InputActionPlugin);
|
||||
};
|
||||
|
||||
Game::initialize()
|
||||
.await
|
||||
.with_plugin(lyra_engine::DefaultPlugins)
|
||||
.with_plugin(setup_scene_plugin)
|
||||
.with_plugin(action_handler_plugin)
|
||||
//.with_plugin(camera_debug_plugin)
|
||||
.with_plugin(FreeFlyCameraPlugin)
|
||||
.run()
|
||||
.await;
|
||||
}
|
||||
|
||||
fn setup_scene_plugin(game: &mut Game) {
|
||||
let world = game.world_mut();
|
||||
let resman = world.get_resource_mut::<ResourceManager>();
|
||||
|
||||
/* let camera_gltf = resman
|
||||
.request::<Gltf>("../assets/AntiqueCamera.glb")
|
||||
.unwrap();
|
||||
|
||||
camera_gltf.wait_recurse_dependencies_load();
|
||||
let camera_mesh = &camera_gltf.data_ref().unwrap().scenes[0];
|
||||
drop(resman);
|
||||
|
||||
world.spawn((
|
||||
camera_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, -5.0, -2.0),
|
||||
)); */
|
||||
|
||||
let cube_gltf = resman
|
||||
.request::<Gltf>("../assets/cube-texture-embedded.gltf")
|
||||
.unwrap();
|
||||
|
||||
cube_gltf.wait_recurse_dependencies_load();
|
||||
let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0];
|
||||
|
||||
let palm_tree_platform_gltf = resman
|
||||
.request::<Gltf>("../assets/shadows-platform-palmtree.glb")
|
||||
.unwrap();
|
||||
|
||||
palm_tree_platform_gltf.wait_recurse_dependencies_load();
|
||||
let palm_tree_platform_mesh = &palm_tree_platform_gltf.data_ref().unwrap().scenes[0];
|
||||
|
||||
drop(resman);
|
||||
|
||||
// cube in the air
|
||||
/* world.spawn((
|
||||
cube_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, -2.0, -5.0),
|
||||
));
|
||||
|
||||
// cube really high in the air
|
||||
world.spawn((
|
||||
cube_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(-6.0, 0.0, -5.0),
|
||||
));
|
||||
|
||||
// cube on the right, on the ground
|
||||
world.spawn((
|
||||
cube_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(3.0, -3.75, -5.0),
|
||||
));
|
||||
|
||||
world.spawn((
|
||||
platform_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
//Transform::from_xyz(0.0, -5.0, -5.0),
|
||||
Transform::new(math::vec3(0.0, -5.0, -5.0), math::Quat::IDENTITY, math::vec3(5.0, 1.0, 5.0)),
|
||||
)); */
|
||||
|
||||
world.spawn((
|
||||
palm_tree_platform_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(5.0, -15.0, 0.0),
|
||||
//Transform::new(math::vec3(0.0, -5.0, -5.0), math::Quat::IDENTITY, math::vec3(5.0, 1.0, 5.0)),
|
||||
));
|
||||
|
||||
//shadows-platform-palmtree.glb
|
||||
|
||||
{
|
||||
let mut light_tran = Transform::from_xyz(0.0, 0.0, 0.0);
|
||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||
light_tran.rotate_x(math::Angle::Degrees(-45.0));
|
||||
light_tran.rotate_y(math::Angle::Degrees(-35.0));
|
||||
world.spawn((
|
||||
//cube_mesh.clone(),
|
||||
DirectionalLight {
|
||||
enabled: true,
|
||||
color: Vec3::new(1.0, 0.95, 0.9),
|
||||
intensity: 0.9,
|
||||
},
|
||||
ShadowCasterSettings {
|
||||
filtering_mode: ShadowFilteringMode::Pcss,
|
||||
pcf_samples_num: 64,
|
||||
pcss_blocker_search_samples: 36,
|
||||
constant_depth_bias_scale: 5.0,
|
||||
..Default::default()
|
||||
},
|
||||
light_tran,
|
||||
));
|
||||
|
||||
/* world.spawn((
|
||||
cube_mesh.clone(),
|
||||
PointLight {
|
||||
enabled: true,
|
||||
color: Vec3::new(0.133, 0.098, 0.91),
|
||||
intensity: 2.0,
|
||||
range: 10.0,
|
||||
..Default::default()
|
||||
},
|
||||
ShadowCasterSettings {
|
||||
filtering_mode: ShadowFilteringMode::Pcf,
|
||||
..Default::default()
|
||||
},
|
||||
Transform::new(
|
||||
Vec3::new(4.0 - 1.43, -13.0, 1.53),
|
||||
Quat::IDENTITY,
|
||||
Vec3::new(0.5, 0.5, 0.5),
|
||||
),
|
||||
)); */
|
||||
|
||||
let t = Transform::new(
|
||||
Vec3::new(4.0 - 1.43, -13.0, 0.0),
|
||||
//Vec3::new(-5.0, 1.0, -0.28),
|
||||
//Vec3::new(-10.0, 0.94, -0.28),
|
||||
|
||||
Quat::from_euler(math::EulerRot::XYZ, 0.0, math::Angle::Degrees(-45.0).to_radians(), 0.0),
|
||||
Vec3::new(0.15, 0.15, 0.15),
|
||||
);
|
||||
|
||||
world.spawn((
|
||||
SpotLight {
|
||||
enabled: true,
|
||||
color: Vec3::new(1.0, 0.0, 0.0),
|
||||
intensity: 3.0,
|
||||
range: 4.5,
|
||||
//cutoff: math::Angle::Degrees(45.0),
|
||||
..Default::default()
|
||||
},
|
||||
/* ShadowCasterSettings {
|
||||
filtering_mode: ShadowFilteringMode::Pcf,
|
||||
..Default::default()
|
||||
}, */
|
||||
WorldTransform::from(t),
|
||||
t,
|
||||
//cube_mesh.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut camera = CameraComponent::new_3d();
|
||||
camera.transform.translation = math::Vec3::new(-1.0, -10.0, -1.5);
|
||||
camera.transform.rotate_x(math::Angle::Degrees(-27.0));
|
||||
camera.transform.rotate_y(math::Angle::Degrees(-90.0));
|
||||
|
||||
world.spawn((camera, FreeFlyCamera::default()));
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "simple_scene"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lyra-engine = { path = "../../", features = ["tracy"] }
|
||||
anyhow = "1.0.75"
|
||||
async-std = "1.12.0"
|
||||
tracing = "0.1.37"
|
|
@ -0,0 +1,59 @@
|
|||
---Return the userdata's name from its metatable
|
||||
---@param val userdata
|
||||
---@return string
|
||||
function udname(val)
|
||||
return getmetatable(val).__name
|
||||
end
|
||||
|
||||
function on_init()
|
||||
local cube = world:request_res("../assets/cube-texture-embedded.gltf")
|
||||
print("Loaded textured cube (" .. udname(cube) .. ")")
|
||||
|
||||
cube:wait_until_loaded()
|
||||
local scenes = cube:scenes()
|
||||
local cube_scene = scenes[1]
|
||||
|
||||
local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
|
||||
|
||||
local e = world:spawn(pos, cube_scene)
|
||||
print("spawned entity " .. tostring(e))
|
||||
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) ]]
|
||||
|
||||
---@type number
|
||||
local dt = world:resource(DeltaTime)
|
||||
|
||||
world:view(function (t)
|
||||
t:translate(0, 0.15 * dt, 0)
|
||||
return t
|
||||
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 ]]
|
|
@ -0,0 +1,149 @@
|
|||
use lyra_engine::{
|
||||
assets::{gltf::Gltf, ResourceManager},
|
||||
game::Game,
|
||||
input::{
|
||||
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
|
||||
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
|
||||
},
|
||||
math::{self, Transform, Vec3},
|
||||
render::light::directional::DirectionalLight,
|
||||
scene::{
|
||||
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
|
||||
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
|
||||
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
|
||||
},
|
||||
};
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
let action_handler_plugin = |game: &mut Game| {
|
||||
let action_handler = ActionHandler::builder()
|
||||
.add_layout(LayoutId::from(0))
|
||||
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
|
||||
.add_action("Debug", Action::new(ActionKind::Button))
|
||||
.add_mapping(
|
||||
ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
|
||||
.bind(
|
||||
ACTLBL_MOVE_FORWARD_BACKWARD,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_MOVE_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_LEFT_RIGHT,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_UP_DOWN,
|
||||
&[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
ACTLBL_LOOK_ROLL,
|
||||
&[
|
||||
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
|
||||
],
|
||||
)
|
||||
.bind(
|
||||
"Debug",
|
||||
&[ActionSource::Keyboard(KeyCode::B).into_binding()],
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
.finish();
|
||||
|
||||
let world = game.world_mut();
|
||||
world.add_resource(action_handler);
|
||||
game.with_plugin(InputActionPlugin);
|
||||
};
|
||||
|
||||
Game::initialize()
|
||||
.await
|
||||
.with_plugin(lyra_engine::DefaultPlugins)
|
||||
.with_plugin(setup_scene_plugin)
|
||||
.with_plugin(action_handler_plugin)
|
||||
//.with_plugin(camera_debug_plugin)
|
||||
.with_plugin(FreeFlyCameraPlugin)
|
||||
.run()
|
||||
.await;
|
||||
}
|
||||
|
||||
fn setup_scene_plugin(game: &mut Game) {
|
||||
let world = game.world_mut();
|
||||
let resman = world.get_resource_mut::<ResourceManager>();
|
||||
|
||||
/* let camera_gltf = resman
|
||||
.request::<Gltf>("../assets/AntiqueCamera.glb")
|
||||
.unwrap();
|
||||
|
||||
camera_gltf.wait_recurse_dependencies_load();
|
||||
let camera_mesh = &camera_gltf.data_ref().unwrap().scenes[0];
|
||||
drop(resman);
|
||||
|
||||
world.spawn((
|
||||
camera_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, -5.0, -2.0),
|
||||
)); */
|
||||
|
||||
let cube_gltf = resman
|
||||
.request::<Gltf>("../assets/cube-texture-embedded.gltf")
|
||||
.unwrap();
|
||||
|
||||
cube_gltf.wait_recurse_dependencies_load();
|
||||
let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0];
|
||||
drop(resman);
|
||||
|
||||
world.spawn((
|
||||
cube_mesh.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, 0.0, -2.0),
|
||||
));
|
||||
|
||||
{
|
||||
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
|
||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||
light_tran.rotate_x(math::Angle::Degrees(-45.0));
|
||||
light_tran.rotate_y(math::Angle::Degrees(25.0));
|
||||
world.spawn((
|
||||
DirectionalLight {
|
||||
enabled: true,
|
||||
color: Vec3::ONE,
|
||||
intensity: 0.15, //..Default::default()
|
||||
},
|
||||
light_tran,
|
||||
));
|
||||
}
|
||||
|
||||
let mut camera = CameraComponent::new_3d();
|
||||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
||||
world.spawn((camera, FreeFlyCamera::default()));
|
||||
}
|
|
@ -6,10 +6,10 @@ 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 = ["lua_scripting"] }
|
||||
lyra-scripting = { path = "../../lyra-scripting", features = ["lua", "teal"] }
|
||||
lyra-engine = { path = "../../", version = "0.0.1" }
|
||||
#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"
|
||||
tracing = "0.1.37"
|
||||
fps_counter = "2.0.0"
|
||||
tracing = "0.1.37"
|
|
@ -1,102 +0,0 @@
|
|||
use lyra_engine::{
|
||||
ecs::{query::{Res, View}, Component}, game::Game, input::ActionHandler, math::{EulerRot, Quat, Vec3}, plugin::Plugin, scene::CameraComponent, DeltaTime
|
||||
};
|
||||
|
||||
/* enum FreeFlyCameraActions {
|
||||
MoveForwardBackward,
|
||||
MoveLeftRight,
|
||||
MoveUpDown,
|
||||
LookLeftRight,
|
||||
LookUpDown,
|
||||
LookRoll,
|
||||
} */
|
||||
|
||||
#[derive(Clone, Component)]
|
||||
pub struct FreeFlyCamera {
|
||||
pub speed: f32,
|
||||
pub slow_speed_factor: f32,
|
||||
pub look_speed: f32,
|
||||
pub mouse_sensitivity: f32,
|
||||
pub look_with_keys: bool,
|
||||
}
|
||||
|
||||
impl Default for FreeFlyCamera {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
speed: 4.0,
|
||||
slow_speed_factor: 0.25,
|
||||
look_speed: 0.3,
|
||||
mouse_sensitivity: 1.0,
|
||||
look_with_keys: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FreeFlyCamera {
|
||||
#[allow(dead_code)]
|
||||
pub fn new(speed: f32, slow_speed_factor: f32, look_speed: f32, mouse_sensitivity: f32, look_with_keys: bool) -> Self {
|
||||
Self {
|
||||
speed,
|
||||
slow_speed_factor,
|
||||
look_speed,
|
||||
mouse_sensitivity,
|
||||
look_with_keys,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_fly_camera_controller(delta_time: Res<DeltaTime>, handler: Res<ActionHandler>, view: View<(&mut CameraComponent, &FreeFlyCamera)>) -> anyhow::Result<()> {
|
||||
let delta_time = **delta_time;
|
||||
for (mut cam, fly) in view.into_iter() {
|
||||
let forward = cam.transform.forward();
|
||||
let left = cam.transform.left();
|
||||
let up = Vec3::Y;
|
||||
|
||||
let move_y = handler.get_axis_modifier("MoveUpDown").unwrap_or(0.0);
|
||||
let move_x = handler.get_axis_modifier("MoveLeftRight").unwrap_or(0.0);
|
||||
let move_z = handler.get_axis_modifier("MoveForwardBackward").unwrap_or(0.0);
|
||||
|
||||
let mut velocity = Vec3::ZERO;
|
||||
velocity += move_y * up;
|
||||
velocity += move_x * left;
|
||||
velocity += move_z * forward;
|
||||
|
||||
if velocity != Vec3::ZERO {
|
||||
cam.transform.translation += velocity.normalize() * fly.speed * delta_time; // TODO: speeding up
|
||||
}
|
||||
|
||||
let motion_x = handler.get_axis_modifier("LookLeftRight").unwrap_or(0.0);
|
||||
let motion_y = handler.get_axis_modifier("LookUpDown").unwrap_or(0.0);
|
||||
let motion_z = handler.get_axis_modifier("LookRoll").unwrap_or(0.0);
|
||||
|
||||
let mut camera_rot = Vec3::ZERO;
|
||||
camera_rot.y -= motion_x * fly.mouse_sensitivity;
|
||||
camera_rot.x -= motion_y * fly.mouse_sensitivity;
|
||||
camera_rot.z -= motion_z * fly.mouse_sensitivity;
|
||||
|
||||
if camera_rot != Vec3::ZERO {
|
||||
let look_velocity = camera_rot * fly.look_speed * delta_time;
|
||||
|
||||
let (mut y, mut x, _) = cam.transform.rotation.to_euler(EulerRot::YXZ);
|
||||
x += look_velocity.x;
|
||||
y += look_velocity.y;
|
||||
x = x.clamp(-1.54, 1.54);
|
||||
|
||||
// rotation is not commutative, keep this order to avoid unintended roll
|
||||
cam.transform.rotation = Quat::from_axis_angle(Vec3::Y, y)
|
||||
* Quat::from_axis_angle(Vec3::X, x);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A plugin that adds the free fly camera controller system to the world. It is expected that
|
||||
/// there is a [`FreeFlyCamera`] in the world, if there isn't, the camera would not move.
|
||||
pub struct FreeFlyCameraPlugin;
|
||||
|
||||
impl Plugin for FreeFlyCameraPlugin {
|
||||
fn setup(&self, game: &mut Game) {
|
||||
game.with_system("free_fly_camera_system", free_fly_camera_controller, &[]);
|
||||
}
|
||||
}
|
|
@ -1,25 +1,13 @@
|
|||
use std::ptr::NonNull;
|
||||
use std::{ptr::NonNull, thread, time::Duration};
|
||||
|
||||
use lyra_engine::{math::{self, Vec3}, math::Transform, input::{KeyCode, ActionHandler, Action, ActionKind, LayoutId, ActionMapping, ActionSource, ActionMappingId, InputActionPlugin, MouseInput, MouseAxis, CommonActionLabel}, game::Game, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct, ecs::{system::{Criteria, CriteriaSchedule, BatchedSystem, IntoSystem}, World, Component}, DeltaTime, scene::{ModelComponent, CameraComponent}, lua::{LuaScriptingPlugin, LuaScript}, Script, ScriptList};
|
||||
use lyra_engine::assets::{ResourceManager, Model};
|
||||
|
||||
mod free_fly_camera;
|
||||
use free_fly_camera::{FreeFlyCameraPlugin, FreeFlyCamera};
|
||||
|
||||
#[derive(Clone, Copy, Hash, Debug)]
|
||||
pub enum ActionLabel {
|
||||
MoveForwardBackward,
|
||||
MoveLeftRight,
|
||||
MoveUpDown,
|
||||
LookLeftRight,
|
||||
LookUpDown,
|
||||
LookRoll,
|
||||
}
|
||||
use lyra_engine::{assets::gltf::Gltf, change_tracker::Ct, ecs::{query::{Res, View}, system::{Criteria, CriteriaSchedule, IntoSystem}, Component, World}, game::Game, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Quat, Transform, Vec3}, render::{light::{directional::DirectionalLight, PointLight, SpotLight}, window::{CursorGrabMode, WindowOptions}}, scene::{self, CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN}, DeltaTime};
|
||||
use lyra_engine::assets::ResourceManager;
|
||||
|
||||
struct FixedTimestep {
|
||||
max_tps: u32,
|
||||
fixed_time: f32,
|
||||
accumulator: f32,
|
||||
old_dt: Option<DeltaTime>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -29,6 +17,7 @@ impl FixedTimestep {
|
|||
max_tps,
|
||||
fixed_time: Self::calc_fixed_time(max_tps),
|
||||
accumulator: 0.0,
|
||||
old_dt: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -65,6 +54,21 @@ impl Criteria for FixedTimestep {
|
|||
|
||||
CriteriaSchedule::No
|
||||
}
|
||||
|
||||
fn modify_world(&mut self, mut world: NonNull<World>) {
|
||||
let world = unsafe { world.as_mut() };
|
||||
self.old_dt = world.try_get_resource().map(|r| *r);
|
||||
|
||||
world.add_resource(DeltaTime::from(self.fixed_time));
|
||||
}
|
||||
|
||||
fn undo_world_modifications(&mut self, mut world: NonNull<World>) {
|
||||
let world = unsafe { world.as_mut() };
|
||||
world.add_resource(
|
||||
self.old_dt
|
||||
.expect("DeltaTime resource was somehow never got from the world"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -82,34 +86,37 @@ async fn main() {
|
|||
window_options.cursor_visible = false; */
|
||||
}
|
||||
|
||||
let mut resman = world.get_resource_mut::<ResourceManager>();
|
||||
let resman = world.get_resource_mut::<ResourceManager>();
|
||||
//let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
|
||||
//let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap();
|
||||
//let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap();
|
||||
let cube_model = resman.request::<Model>("assets/texture-sep/texture-sep.gltf").unwrap();
|
||||
let crate_model = resman.request::<Model>("assets/crate/crate.gltf").unwrap();
|
||||
//let sponza_model = resman.request::<Model>("assets/sponza/Sponza.gltf").unwrap();
|
||||
let cube_gltf = resman.request::<Gltf>("../assets/texture-sep/texture-sep.gltf").unwrap();
|
||||
/*let crate_gltf = resman.request::<Gltf>("assets/crate/crate.gltf").unwrap();
|
||||
|
||||
let separate_gltf = resman.request::<Gltf>("assets/pos-testing/child-node-cubes.glb").unwrap(); */
|
||||
//drop(resman);
|
||||
|
||||
cube_gltf.wait_recurse_dependencies_load();
|
||||
let cube_mesh = &cube_gltf.data_ref()
|
||||
.unwrap().meshes[0];
|
||||
/* let crate_mesh = &crate_gltf.data_ref()
|
||||
.unwrap().meshes[0];
|
||||
|
||||
let separate_scene = &separate_gltf.data_ref()
|
||||
.unwrap().scenes[0]; */
|
||||
|
||||
let sponza_model = resman.request::<Gltf>("../assets/sponza/Sponza.gltf").unwrap();
|
||||
drop(resman);
|
||||
|
||||
/* world.spawn((
|
||||
ModelComponent(antique_camera_model),
|
||||
Transform::from_xyz(0.0, -5.0, -10.0),
|
||||
)); */
|
||||
sponza_model.wait_recurse_dependencies_load();
|
||||
let sponza_scene = &sponza_model.data_ref()
|
||||
.unwrap().scenes[0];
|
||||
|
||||
/* world.spawn((
|
||||
ModelComponent(sponza_model),
|
||||
world.spawn((
|
||||
sponza_scene.clone(),
|
||||
WorldTransform::default(),
|
||||
Transform::from_xyz(0.0, 0.0, 0.0),
|
||||
)); */
|
||||
|
||||
{
|
||||
let cube_tran = Transform::from_xyz(-3.5, 0.0, -8.0);
|
||||
//cube_tran.rotate_y(math::Angle::Degrees(180.0));
|
||||
world.spawn((
|
||||
cube_tran,
|
||||
ModelComponent(crate_model.clone()),
|
||||
CubeFlag,
|
||||
));
|
||||
}
|
||||
));
|
||||
|
||||
{
|
||||
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
|
||||
|
@ -118,37 +125,105 @@ async fn main() {
|
|||
light_tran.rotate_y(math::Angle::Degrees(25.0));
|
||||
world.spawn((
|
||||
DirectionalLight {
|
||||
color: Vec3::new(1.0, 1.0, 1.0),
|
||||
ambient: 0.3,
|
||||
diffuse: 1.0,
|
||||
specular: 1.3,
|
||||
enabled: true,
|
||||
color: Vec3::ONE,
|
||||
intensity: 0.35
|
||||
//..Default::default()
|
||||
},
|
||||
light_tran,
|
||||
ModelComponent(cube_model.clone()),
|
||||
));
|
||||
}
|
||||
|
||||
{
|
||||
let mut light_tran = Transform::from_xyz(-3.5, 0.2, -4.5);
|
||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||
let t = Transform::new(
|
||||
//Vec3::new(-5.0, 1.0, -1.28),
|
||||
Vec3::new(-5.0, 1.0, -0.0),
|
||||
//Vec3::new(-10.0, 0.94, -0.28),
|
||||
|
||||
Quat::IDENTITY,
|
||||
Vec3::new(0.25, 0.25, 0.25),
|
||||
);
|
||||
|
||||
world.spawn((
|
||||
PointLight {
|
||||
enabled: true,
|
||||
color: Vec3::new(0.0, 0.0, 1.0),
|
||||
intensity: 1.0,
|
||||
range: 2.0,
|
||||
..Default::default()
|
||||
},
|
||||
WorldTransform::from(t),
|
||||
t,
|
||||
cube_mesh.clone(),
|
||||
));
|
||||
|
||||
let t = Transform::new(
|
||||
Vec3::new(-3.0, 0.2, -1.5),
|
||||
//Vec3::new(-5.0, 1.0, -0.28),
|
||||
//Vec3::new(-10.0, 0.94, -0.28),
|
||||
|
||||
Quat::IDENTITY,
|
||||
Vec3::new(0.15, 0.15, 0.15),
|
||||
);
|
||||
|
||||
world.spawn((
|
||||
PointLight {
|
||||
enabled: true,
|
||||
color: Vec3::new(0.0, 0.5, 1.0),
|
||||
intensity: 1.0,
|
||||
range: 1.0,
|
||||
..Default::default()
|
||||
},
|
||||
WorldTransform::from(t),
|
||||
t,
|
||||
cube_mesh.clone(),
|
||||
));
|
||||
|
||||
let t = Transform::new(
|
||||
Vec3::new(0.0, 0.2, -1.5),
|
||||
//Vec3::new(-5.0, 1.0, -0.28),
|
||||
//Vec3::new(-10.0, 0.94, -0.28),
|
||||
|
||||
Quat::IDENTITY,
|
||||
Vec3::new(0.15, 0.15, 0.15),
|
||||
);
|
||||
|
||||
world.spawn((
|
||||
SpotLight {
|
||||
color: Vec3::new(1.0, 0.2, 0.2),
|
||||
cutoff: math::Angle::Degrees(12.5),
|
||||
outer_cutoff: math::Angle::Degrees(17.5),
|
||||
|
||||
constant: 1.0,
|
||||
linear: 0.007,
|
||||
quadratic: 0.0002,
|
||||
|
||||
ambient: 0.0,
|
||||
diffuse: 7.0,
|
||||
specular: 1.0,
|
||||
enabled: true,
|
||||
color: Vec3::new(1.0, 0.0, 0.0),
|
||||
intensity: 1.0,
|
||||
range: 1.5,
|
||||
//cutoff: math::Angle::Degrees(45.0),
|
||||
..Default::default()
|
||||
},
|
||||
Transform::from(light_tran),
|
||||
ModelComponent(cube_model.clone()),
|
||||
WorldTransform::from(t),
|
||||
t,
|
||||
cube_mesh.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
/* {
|
||||
let mut light_tran = Transform::from_xyz(2.0, 2.5, -9.5);
|
||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||
world.spawn((
|
||||
PointLight {
|
||||
color: Vec3::new(0.0, 0.0, 1.0),
|
||||
|
||||
intensity: 3.3,
|
||||
|
||||
constant: 1.0,
|
||||
linear: 0.09,
|
||||
quadratic: 0.032,
|
||||
|
||||
ambient: 0.2,
|
||||
diffuse: 1.0,
|
||||
specular: 1.3,
|
||||
},
|
||||
Transform::from(light_tran),
|
||||
cube_mesh.clone(),
|
||||
));
|
||||
} */
|
||||
|
||||
/* {
|
||||
let mut light_tran = Transform::from_xyz(-5.0, 2.5, -9.5);
|
||||
|
@ -172,128 +247,87 @@ async fn main() {
|
|||
));
|
||||
} */
|
||||
|
||||
{
|
||||
let mut light_tran = Transform::from_xyz(2.0, 2.5, -9.5);
|
||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||
world.spawn((
|
||||
PointLight {
|
||||
color: Vec3::new(0.0, 0.0, 1.0),
|
||||
|
||||
intensity: 3.3,
|
||||
|
||||
constant: 1.0,
|
||||
linear: 0.09,
|
||||
quadratic: 0.032,
|
||||
|
||||
ambient: 0.2,
|
||||
diffuse: 1.0,
|
||||
specular: 1.3,
|
||||
},
|
||||
Transform::from(light_tran),
|
||||
ModelComponent(cube_model),
|
||||
));
|
||||
}
|
||||
|
||||
let mut camera = CameraComponent::new_3d();
|
||||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
||||
// these values were taken by manually positioning the camera in the scene.
|
||||
camera.transform = Transform::new(
|
||||
Vec3::new(-10.0, 0.94, -0.28),
|
||||
Quat::from_xyzw(0.03375484, -0.7116095, 0.0342693, 0.70092666),
|
||||
Vec3::ONE
|
||||
);
|
||||
//camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
||||
world.spawn(( camera, FreeFlyCamera::default() ));
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let fps_system = |world: &mut World| -> anyhow::Result<()> {
|
||||
let mut counter = world.get_resource_mut::<fps_counter::FPSCounter>();
|
||||
let camera_debug_plugin = move |game: &mut Game| {
|
||||
let sys = |handler: Res<ActionHandler>, view: View<&mut CameraComponent>| -> anyhow::Result<()> {
|
||||
if handler.was_action_just_pressed("Debug") {
|
||||
for mut cam in view.into_iter() {
|
||||
cam.debug = !cam.debug;
|
||||
}
|
||||
}
|
||||
|
||||
let fps = counter.tick();
|
||||
Ok(())
|
||||
};
|
||||
|
||||
println!("FPS: {fps}");
|
||||
|
||||
Ok(())
|
||||
};
|
||||
let fps_plugin = move |game: &mut Game| {
|
||||
let world = game.world_mut();
|
||||
world.add_resource(fps_counter::FPSCounter::new());
|
||||
};
|
||||
|
||||
let spin_system = |world: &mut World| -> anyhow::Result<()> {
|
||||
const SPEED: f32 = 4.0;
|
||||
let delta_time = **world.get_resource::<DeltaTime>();
|
||||
|
||||
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 Transform, &mut SpotLight)>() {
|
||||
let t = &mut transform;
|
||||
t.rotate_x(math::Angle::Degrees(SPEED * delta_time));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let jiggle_plugin = move |game: &mut Game| {
|
||||
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("camera_debug_trigger", sys, &[]);
|
||||
game.with_system("update_world_transforms", scene::system_update_world_transforms, &[]);
|
||||
};
|
||||
|
||||
let action_handler_plugin = |game: &mut Game| {
|
||||
/* let action_handler = ActionHandler::builder()
|
||||
let action_handler = ActionHandler::builder()
|
||||
.add_layout(LayoutId::from(0))
|
||||
|
||||
.add_action(CommonActionLabel::MoveForwardBackward, Action::new(ActionKind::Axis))
|
||||
.add_action(CommonActionLabel::MoveLeftRight, Action::new(ActionKind::Axis))
|
||||
.add_action(CommonActionLabel::MoveUpDown, Action::new(ActionKind::Axis))
|
||||
.add_action(CommonActionLabel::LookLeftRight, Action::new(ActionKind::Axis))
|
||||
.add_action(CommonActionLabel::LookUpDown, Action::new(ActionKind::Axis))
|
||||
.add_action(CommonActionLabel::LookRoll, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
|
||||
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
|
||||
.add_action("Debug", Action::new(ActionKind::Button))
|
||||
|
||||
.add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
|
||||
.bind(CommonActionLabel::MoveForwardBackward, &[
|
||||
.bind(ACTLBL_MOVE_FORWARD_BACKWARD, &[
|
||||
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0)
|
||||
])
|
||||
.bind(CommonActionLabel::MoveLeftRight, &[
|
||||
.bind(ACTLBL_MOVE_LEFT_RIGHT, &[
|
||||
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0)
|
||||
])
|
||||
.bind(CommonActionLabel::MoveUpDown, &[
|
||||
.bind(ACTLBL_MOVE_UP_DOWN, &[
|
||||
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
|
||||
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0)
|
||||
])
|
||||
.bind(CommonActionLabel::LookLeftRight, &[
|
||||
.bind(ACTLBL_LOOK_LEFT_RIGHT, &[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
|
||||
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickX)).into_binding(),
|
||||
])
|
||||
.bind(CommonActionLabel::LookUpDown, &[
|
||||
.bind(ACTLBL_LOOK_UP_DOWN, &[
|
||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
||||
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
|
||||
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickY)).into_binding(),
|
||||
])
|
||||
.bind(CommonActionLabel::LookRoll, &[
|
||||
.bind(ACTLBL_LOOK_ROLL, &[
|
||||
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
|
||||
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
|
||||
])
|
||||
.bind("Debug", &[
|
||||
ActionSource::Keyboard(KeyCode::B).into_binding(),
|
||||
])
|
||||
.finish()
|
||||
);
|
||||
).finish();
|
||||
|
||||
let world = game.world_mut();
|
||||
world.add_resource(action_handler); */
|
||||
world.add_resource(action_handler);
|
||||
game.with_plugin(InputActionPlugin);
|
||||
};
|
||||
|
||||
let script_test_plugin = |game: &mut Game| {
|
||||
/* let script_test_plugin = |game: &mut Game| {
|
||||
game.with_plugin(LuaScriptingPlugin);
|
||||
|
||||
let world = game.world_mut();
|
||||
|
@ -306,15 +340,15 @@ async fn main() {
|
|||
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(script_test_plugin)
|
||||
//.with_plugin(fps_plugin)
|
||||
.with_plugin(jiggle_plugin)
|
||||
.with_plugin(camera_debug_plugin)
|
||||
.with_plugin(FreeFlyCameraPlugin)
|
||||
.run().await;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"rdocCaptureSettings": 1,
|
||||
"settings": {
|
||||
"autoStart": true,
|
||||
"autoStart": false,
|
||||
"commandLine": "",
|
||||
"environment": [
|
||||
],
|
||||
"executable": "/media/data_drive/Development/Rust/lyra-test/engine/target/debug/testbed",
|
||||
"executable": "/media/data_drive/Development/Rust/lyra-engine/target/debug/testbed",
|
||||
"inject": false,
|
||||
"numQueuedFrames": 1,
|
||||
"numQueuedFrames": 0,
|
||||
"options": {
|
||||
"allowFullscreen": true,
|
||||
"allowVSync": true,
|
||||
|
@ -22,7 +22,7 @@
|
|||
"softMemoryLimit": 0,
|
||||
"verifyBufferAccess": false
|
||||
},
|
||||
"queuedFrameCap": 5,
|
||||
"workingDir": "/media/data_drive/Development/Rust/lyra-test/engine/examples/testbed"
|
||||
"queuedFrameCap": 0,
|
||||
"workingDir": "/media/data_drive/Development/Rust/lyra-engine/examples/testbed"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ lyra-math = { path = "../lyra-math", optional = true }
|
|||
anyhow = "1.0.75"
|
||||
thiserror = "1.0.50"
|
||||
paste = "1.0.14"
|
||||
atomic_refcell = "0.1.13"
|
||||
tracing = "0.1.37"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5" # used for tests
|
||||
|
|
|
@ -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};
|
||||
|
||||
use crate::{world::ArchetypeEntityId, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick, Entity};
|
||||
use crate::{bundle::Bundle, component_info::ComponentInfo, world::ArchetypeEntityId, DynTypeId, Entity, Tick};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ComponentColumn {
|
||||
|
@ -19,7 +19,9 @@ impl Drop for ComponentColumn {
|
|||
|
||||
unsafe {
|
||||
// TODO: trigger drop on the components
|
||||
let layout = self.info.layout();
|
||||
// SAFETY: The size of the data buffer is the capcity times the size of a component
|
||||
let size = self.info.layout().size() * self.capacity;
|
||||
let layout = Layout::from_size_align_unchecked(size, self.info.layout().align());
|
||||
dealloc(data, layout);
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +57,6 @@ impl ComponentColumn {
|
|||
/// Set a component from pointer at an entity index.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This column must have space to fit the component, if it does not have room it will panic.
|
||||
pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
||||
assert!(entity_index < self.capacity);
|
||||
|
@ -76,6 +77,23 @@ impl ComponentColumn {
|
|||
}
|
||||
}
|
||||
|
||||
/// Inserts an entity and its component at a specific index.
|
||||
pub unsafe fn insert_entity(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
||||
assert!(entity_index < self.capacity);
|
||||
|
||||
let mut data = self.data.borrow_mut();
|
||||
let data = data.deref_mut();
|
||||
|
||||
let size = self.info.layout().size();
|
||||
let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
||||
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size);
|
||||
|
||||
// check if a component spot is being set twice and that the entity's tick is
|
||||
// already stored
|
||||
self.entity_ticks.push(tick);
|
||||
self.len += 1;
|
||||
}
|
||||
|
||||
/// Get a component at an entities index.
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -152,6 +170,8 @@ impl ComponentColumn {
|
|||
/// Removes a component from the column, freeing it, and returning the old index of the entity that took its place in the column.
|
||||
pub unsafe fn remove_component(&mut self, entity_index: usize, tick: &Tick) -> Option<usize> {
|
||||
let _ = tick; // may be used at some point
|
||||
|
||||
debug_assert!(self.len > 0, "There are no entities in the Archetype to remove from!");
|
||||
|
||||
let mut data = self.data.borrow_mut();
|
||||
let data = data.deref_mut();
|
||||
|
@ -170,8 +190,7 @@ impl ComponentColumn {
|
|||
mem::swap(&mut old_comp_ptr, &mut new_comp_ptr); // new_comp_ptr is now the old ptr
|
||||
|
||||
// make sure to keep entity indexes correct in the ticks list as well
|
||||
self.entity_ticks.swap(moved_index, entity_index);
|
||||
self.entity_ticks.pop();
|
||||
self.entity_ticks.swap_remove(entity_index);
|
||||
|
||||
Some(moved_index)
|
||||
} else { None };
|
||||
|
@ -276,38 +295,48 @@ impl Archetype {
|
|||
self.capacity = new_cap;
|
||||
}
|
||||
|
||||
debug_assert_eq!(self.entity_ids.len(), self.entities.len(), "Somehow the Archetype's entity storage got unsynced");
|
||||
self.ensure_synced();
|
||||
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
|
||||
self.entity_ids.insert(entity, entity_index);
|
||||
self.entities.push(entity);
|
||||
|
||||
bundle.take(|data, type_id, _size| {
|
||||
let col = self.get_column_mut(type_id).unwrap();
|
||||
unsafe { col.set_at(entity_index.0 as usize, data, *tick); }
|
||||
col.len += 1;
|
||||
bundle.take(|data, type_id, info| {
|
||||
self.put_component_at(tick, data, type_id, info.layout().size(), entity_index.0 as _);
|
||||
});
|
||||
|
||||
entity_index
|
||||
}
|
||||
|
||||
/// Removes an entity from the Archetype and frees its components. Returns the entity record
|
||||
/// that took its place in the component column.
|
||||
pub(crate) fn put_component_at(&mut self, tick: &Tick, ptr: NonNull<u8>, type_id: DynTypeId, size: usize, index: usize) {
|
||||
let _ = size;
|
||||
|
||||
let col = self.get_column_mut(type_id).unwrap();
|
||||
//unsafe { col.set_at(index, ptr, *tick) };
|
||||
unsafe { col.insert_entity(index, ptr, *tick); }
|
||||
}
|
||||
|
||||
/// Removes an entity from the Archetype and frees its components.
|
||||
///
|
||||
/// Inside the component columns, the entities are swap-removed. Meaning that the last
|
||||
/// entity in the column is moved in the position of the entity that was removed.
|
||||
/// If there was an entity that was swapped, this function returns the entity, and its
|
||||
/// new index in the archetype that was put in place of the removed entity.
|
||||
pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> {
|
||||
let entity_index = *self.entity_ids.get(&entity)
|
||||
let entity_index = self.entity_ids.remove(&entity)
|
||||
.expect("The entity is not in this Archetype!");
|
||||
let mut removed_entity: Option<(Entity, ArchetypeEntityId)> = None;
|
||||
|
||||
for c in self.columns.iter_mut() {
|
||||
let moved_entity = unsafe { c.remove_component(entity_index.0 as usize, tick) };
|
||||
|
||||
if let Some(res) = moved_entity {
|
||||
if let Some(moved_idx) = moved_entity {
|
||||
if let Some((_, aid)) = removed_entity {
|
||||
// Make sure that the moved entity is the same as what was moved in other columns.
|
||||
assert!(res as u64 == aid.0);
|
||||
assert!(moved_idx as u64 == aid.0);
|
||||
} else {
|
||||
// This is the first move, so find the EntityId that points to the column index.
|
||||
let just_removed = self.entities[res];
|
||||
removed_entity = Some((just_removed, ArchetypeEntityId(res as u64)));
|
||||
// This is the first move, so find the Entity that was moved into this index.
|
||||
let just_removed = self.entities[moved_idx];
|
||||
removed_entity = Some((just_removed, ArchetypeEntityId(moved_idx as u64)));
|
||||
}
|
||||
} else {
|
||||
// If there wasn't a moved entity, make sure no other columns moved something.
|
||||
|
@ -316,13 +345,16 @@ impl Archetype {
|
|||
}
|
||||
|
||||
// safe from the .expect at the start of this method.
|
||||
self.entity_ids.remove(&entity).unwrap();
|
||||
if self.entities.len() > 1 {
|
||||
let len = self.entities.len();
|
||||
self.entities.swap(entity_index.0 as _, len - 1);
|
||||
}
|
||||
self.entities.pop().unwrap();
|
||||
//self.entity_ids.remove(&entity).unwrap();
|
||||
|
||||
// update the archetype index of the moved entity
|
||||
if let Some((moved, _old_idx)) = removed_entity {
|
||||
self.entity_ids.insert(moved, entity_index);
|
||||
}
|
||||
|
||||
let removed = self.entities.swap_remove(entity_index.0 as _);
|
||||
assert_eq!(removed, entity);
|
||||
|
||||
// now change the ArchetypeEntityId to be the index that the moved entity was moved into.
|
||||
removed_entity.map(|(e, _a)| (e, entity_index))
|
||||
}
|
||||
|
@ -368,6 +400,11 @@ impl Archetype {
|
|||
self.capacity = new_capacity;
|
||||
}
|
||||
|
||||
/// Attempts to find the column storing components of `type_id`
|
||||
pub fn get_column_at(&self, idx: usize) -> Option<&ComponentColumn> {
|
||||
self.columns.get(idx)
|
||||
}
|
||||
|
||||
/// Attempts to find the column storing components of `type_id`
|
||||
pub fn get_column<I: Into<DynTypeId>>(&self, type_id: I) -> Option<&ComponentColumn> {
|
||||
let type_id = type_id.into();
|
||||
|
@ -390,7 +427,8 @@ impl Archetype {
|
|||
self.capacity = new_cap;
|
||||
}
|
||||
|
||||
debug_assert_eq!(self.entity_ids.len(), self.entities.len(), "Somehow the Archetype's entity storage got unsynced");
|
||||
debug_assert_eq!(self.entity_ids.len(), self.entities.len(),
|
||||
"Somehow the Archetype's entity storage got unsynced");
|
||||
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
|
||||
self.entity_ids.insert(entity, entity_index);
|
||||
self.entities.push(entity);
|
||||
|
@ -402,13 +440,19 @@ impl Archetype {
|
|||
entity_index
|
||||
}
|
||||
|
||||
/// Ensure that the internal entity lists are synced in length
|
||||
pub(crate) fn ensure_synced(&self) {
|
||||
debug_assert_eq!(self.entity_ids.len(), self.entities.len(),
|
||||
"Somehow the Archetype's entity storage got unsynced");
|
||||
}
|
||||
|
||||
/// Moves the entity from this archetype into another one.
|
||||
///
|
||||
/// # Safety
|
||||
/// The entity IS NOT removed from the old archetype. You must manually call [`Archetype::remove_entity`](crate::Archetype).
|
||||
/// It was done this way because I had some borrow check issues when writing [`World::insert`](crate::World)
|
||||
/// related to borrowing mutably from self more than once.
|
||||
/* pub fn move_into<B>(&self, into_arch: &mut Archetype, entity: Entity, new_components: B)
|
||||
/* pub fn move_into<B>(&mut self, entities: &mut Entities, tick: &Tick, into_arch: &mut Archetype, entity: Entity, new_components: B)
|
||||
where
|
||||
B: Bundle
|
||||
{
|
||||
|
@ -418,25 +462,40 @@ impl Archetype {
|
|||
|
||||
// move the existing components into the new archetype
|
||||
for col in self.columns.iter() {
|
||||
let into_col = into_arch.get_column_mut(col.info.type_id).unwrap();
|
||||
let into_col = into_arch.get_column_mut(col.info.type_id()).unwrap();
|
||||
|
||||
// copy from the old column into the new column, then remove it from the old one
|
||||
unsafe {
|
||||
let ptr = col.borrow_ptr();
|
||||
let ptr = NonNull::new_unchecked(ptr.as_ptr()
|
||||
.add(new_index.0 as usize * col.info.layout.size));
|
||||
into_col.set_at(new_index.0 as _, ptr);
|
||||
.add(new_index.0 as usize * col.info.layout().size()));
|
||||
into_col.set_at(new_index.0 as _, ptr, *tick);
|
||||
//into_col.set_at(new_index.0 as _, ptr);
|
||||
}
|
||||
}
|
||||
|
||||
// now move the new components into the new archetype
|
||||
new_components.take(|data, type_id, _size| {
|
||||
let col = into_arch.get_column_mut(type_id).unwrap();
|
||||
unsafe { col.set_at(new_index.0 as _, data); }
|
||||
unsafe { col.set_at(new_index.0 as _, data, *tick); }
|
||||
col.len += 1;
|
||||
});
|
||||
|
||||
//self.remove_entity(entity);
|
||||
if let Some((en, new_idx)) = self.remove_entity(entity, tick) {
|
||||
let moved_rec = Record {
|
||||
id: self.id,
|
||||
index: new_idx,
|
||||
};
|
||||
entities.insert_entity_record(en, moved_rec);
|
||||
}
|
||||
let new_rec = Record {
|
||||
id: into_arch.id,
|
||||
index: new_index,
|
||||
};
|
||||
entities.insert_entity_record(entity, new_rec);
|
||||
|
||||
into_arch.ensure_synced();
|
||||
self.ensure_synced();
|
||||
} */
|
||||
|
||||
/// Returns a borrow to the map used to find the column indices of the entity.
|
||||
|
@ -453,6 +512,36 @@ impl Archetype {
|
|||
pub fn has_entity(&self, e: Entity) -> bool {
|
||||
self.entity_ids.contains_key(&e)
|
||||
}
|
||||
|
||||
/// Extend the Archetype by adding more columns.
|
||||
///
|
||||
/// In order to extend the Archetype, the archetype needs the components for the entities
|
||||
/// it already has. These are provided through the `new_columns` parameter. **If the Vec
|
||||
/// does not have the same amount of bundles in it as the amount of entities in the
|
||||
/// Archetype, it will panic!**
|
||||
pub fn extend<B: Bundle>(&mut self, tick: &Tick, new_columns: Vec<B>) {
|
||||
debug_assert_eq!(new_columns.len(), self.len(), "The amount of provided column does not \
|
||||
match the amount of entities");
|
||||
|
||||
let column_info = new_columns.iter()
|
||||
.next()
|
||||
.unwrap()
|
||||
.info();
|
||||
|
||||
for coli in column_info.into_iter() {
|
||||
let col = unsafe { ComponentColumn::new(coli, self.capacity) };
|
||||
self.columns.push(col);
|
||||
}
|
||||
|
||||
for (eid, bundle) in new_columns.into_iter().enumerate() {
|
||||
bundle.take(|ptr, tyid, _size| {
|
||||
unsafe {
|
||||
let col = self.get_column_mut(tyid).unwrap();
|
||||
col.insert_entity(eid, ptr, tick.clone());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -642,7 +731,7 @@ mod tests {
|
|||
#[test]
|
||||
fn dynamic_archetype() {
|
||||
let layout = Layout::new::<u32>();
|
||||
let info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", layout);
|
||||
let info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), layout);
|
||||
let infos = vec![info.clone()];
|
||||
|
||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), infos);
|
||||
|
@ -666,4 +755,24 @@ mod tests {
|
|||
assert_eq!(unsafe { *ptr.cast::<u32>().as_ref() }, comp);
|
||||
assert_eq!(col.info, info);
|
||||
}
|
||||
|
||||
/// Tests removing an entity from the Archetype when it is the only entity in it.
|
||||
#[test]
|
||||
fn remove_single_entity() {
|
||||
let info = (Vec2::new(0.0, 0.0),).info();
|
||||
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), info);
|
||||
|
||||
let ae = Entity {
|
||||
id: EntityId(0),
|
||||
generation: 0
|
||||
};
|
||||
|
||||
a.add_entity(
|
||||
ae,
|
||||
Vec2::new(10.0, 50.0),
|
||||
&Tick::default()
|
||||
);
|
||||
|
||||
a.remove_entity(ae, &Tick::default());
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use std::{ptr::NonNull, mem::size_of, alloc::Layout};
|
||||
use std::{ptr::NonNull, alloc::Layout};
|
||||
|
||||
use crate::{component::Component, component_info::ComponentInfo, DynTypeId};
|
||||
|
||||
|
@ -11,12 +11,30 @@ pub trait Bundle {
|
|||
|
||||
/// Take the bundle by calling the closure with pointers to each component, its type and size.
|
||||
/// The closure is expected to take ownership of the pointer.
|
||||
fn take(self, f: impl FnMut(NonNull<u8>, DynTypeId, usize));
|
||||
fn take(self, f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo));
|
||||
|
||||
/// Returns a boolean indicating if this Bundle is dynamic. See [`DynamicBundle`]
|
||||
fn is_dynamic(&self) -> bool;
|
||||
}
|
||||
|
||||
impl Bundle for () {
|
||||
fn type_ids(&self) -> Vec<DynTypeId> {
|
||||
vec![DynTypeId::of::<()>()]
|
||||
}
|
||||
|
||||
fn info(&self) -> Vec<ComponentInfo> {
|
||||
vec![ComponentInfo::new::<()>()]
|
||||
}
|
||||
|
||||
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
|
||||
f(NonNull::from(&self).cast(), DynTypeId::of::<()>(), ComponentInfo::new::<()>());
|
||||
}
|
||||
|
||||
fn is_dynamic(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component> Bundle for C {
|
||||
fn type_ids(&self) -> Vec<DynTypeId> {
|
||||
vec![DynTypeId::of::<C>()]
|
||||
|
@ -26,8 +44,8 @@ impl<C: Component> Bundle for C {
|
|||
vec![ComponentInfo::new::<C>()]
|
||||
}
|
||||
|
||||
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, usize)) {
|
||||
f(NonNull::from(&self).cast(), DynTypeId::of::<C>(), size_of::<C>());
|
||||
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
|
||||
f(NonNull::from(&self).cast(), DynTypeId::of::<C>(), ComponentInfo::new::<C>());
|
||||
|
||||
// this must be done to avoid calling drop on heap memory that the component
|
||||
// may manage. So something like a Vec, or HashMap, etc.
|
||||
|
@ -52,12 +70,12 @@ macro_rules! impl_bundle_tuple {
|
|||
vec![$(ComponentInfo::new::<$name>()),+]
|
||||
}
|
||||
|
||||
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, usize)) {
|
||||
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
|
||||
// these names wont follow rust convention, but its a macro so deal with it
|
||||
let ($($name,)+) = self;
|
||||
|
||||
$(
|
||||
f(NonNull::from(&$name).cast(), DynTypeId::of::<$name>(), size_of::<$name>());
|
||||
f(NonNull::from(&$name).cast(), DynTypeId::of::<$name>(), ComponentInfo::new::<$name>());
|
||||
// this must be done to avoid calling drop on heap memory that the component
|
||||
// may manage. So something like a Vec, or HashMap, etc.
|
||||
std::mem::forget($name);
|
||||
|
@ -118,8 +136,6 @@ impl DynamicBundle {
|
|||
where
|
||||
C: Component
|
||||
{
|
||||
let info = ComponentInfo::new::<C>();
|
||||
|
||||
// an owned pointer must be created from the provided component since comp would drop
|
||||
// out of scope and the data would become invalid
|
||||
let ptr = unsafe {
|
||||
|
@ -128,17 +144,43 @@ impl DynamicBundle {
|
|||
let layout = Layout::new::<C>();
|
||||
let alloc_ptr = NonNull::new_unchecked(std::alloc::alloc(layout)).cast::<C>();
|
||||
std::ptr::copy_nonoverlapping(data.as_ptr(), alloc_ptr.as_ptr(), 1);
|
||||
|
||||
// this must be done to avoid calling drop on heap memory that the component
|
||||
// may manage. So something like a Vec, or HashMap, etc.
|
||||
std::mem::forget(comp);
|
||||
|
||||
alloc_ptr.cast()
|
||||
};
|
||||
|
||||
let info = ComponentInfo::new::<C>();
|
||||
self.bundle.push((ptr, info));
|
||||
}
|
||||
|
||||
/// Push an unknown type to the bundle
|
||||
/// Push an unknown type to the end of the bundle.
|
||||
pub fn push_unknown(&mut self, data: NonNull<u8>, info: ComponentInfo) {
|
||||
self.bundle.push((data, info));
|
||||
}
|
||||
|
||||
/// Push a bundle to the end of this dynamic bundle.
|
||||
pub fn push_bundle<B>(&mut self, bundle: B)
|
||||
where
|
||||
B: Bundle
|
||||
{
|
||||
bundle.take(|ptr, _, info| {
|
||||
// unfortunately the components in the bundle must be copied since there is no guarantee that
|
||||
// `bundle` lasts for as long as the `DynamicBundle`. If the data wasn't copied, the pointers
|
||||
// could be invalid later.
|
||||
let p = unsafe {
|
||||
let alloc_ptr = NonNull::new_unchecked(std::alloc::alloc(info.layout()));
|
||||
std::ptr::copy_nonoverlapping(ptr.as_ptr(), alloc_ptr.as_ptr(), info.layout().size());
|
||||
|
||||
alloc_ptr
|
||||
};
|
||||
|
||||
self.push_unknown(p, info);
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Bundle for DynamicBundle {
|
||||
|
@ -150,9 +192,9 @@ impl Bundle for DynamicBundle {
|
|||
self.bundle.iter().map(|b| b.1).collect()
|
||||
}
|
||||
|
||||
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, usize)) {
|
||||
for (data, info) in self.bundle.iter() {
|
||||
f(*data, info.type_id(), info.layout().size());
|
||||
fn take(self, mut f: impl FnMut(NonNull<u8>, DynTypeId, ComponentInfo)) {
|
||||
for (data, info) in self.bundle.into_iter() {
|
||||
f(data, info.type_id(), info);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{any::Any, cell::RefMut, collections::VecDeque, ptr::{self, NonNull}};
|
||||
use std::{any::Any, cell::RefMut, mem::{self, MaybeUninit}, ptr::{self, NonNull}};
|
||||
|
||||
use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World};
|
||||
|
||||
|
@ -23,24 +23,73 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
type RunCommand = unsafe fn(cmd: Box<dyn Command>, world: &mut World);
|
||||
type RunCommand = unsafe fn(cmd: *mut (), world: Option<&mut World>) -> usize;
|
||||
|
||||
#[repr(C, packed)]
|
||||
struct PackedCommand<T: Command> {
|
||||
run: RunCommand,
|
||||
cmd: T,
|
||||
}
|
||||
|
||||
/// Stores a queue of commands that will get executed after the system is ran.
|
||||
///
|
||||
/// This struct can be inserted as a resource into the world, and the commands will be
|
||||
/// executed by the [`GraphExecutor`](crate::system::GraphExecutor) after the system is executed.
|
||||
#[derive(Default)]
|
||||
pub struct CommandQueue(VecDeque<(RunCommand, Box<dyn Command>)>);
|
||||
pub struct CommandQueue {
|
||||
data: Vec<MaybeUninit<u8>>,
|
||||
}
|
||||
|
||||
impl CommandQueue {
|
||||
/// Execute the commands in the queue.
|
||||
///
|
||||
/// If `world` is `None`, the commands will just be dropped and the memory freed.
|
||||
fn execute(&mut self, mut world: Option<&mut World>) {
|
||||
let range = self.data.as_mut_ptr_range();
|
||||
let mut current = range.start;
|
||||
let end = range.end;
|
||||
|
||||
while current < end {
|
||||
// Retrieve the runner for the command.
|
||||
// Safety: current pointer will either be the start of the buffer, or at the start of a new PackedCommand
|
||||
let run_fn = unsafe { current.cast::<RunCommand>().read_unaligned() };
|
||||
|
||||
// Retrieves the pointer to the command which is just after RunCommand due to PackedCommand.
|
||||
// Safety: PackedCommand is repr C and packed, so it will be right after the RunCommand.
|
||||
current = unsafe { current.add(mem::size_of::<RunCommand>()) };
|
||||
|
||||
// Now run the command, providing the type erased pointer to the command.
|
||||
let read_size = unsafe { run_fn(current.cast(), world.as_deref_mut()) };
|
||||
|
||||
// The pointer is added to so that it is just after the command that was ran.
|
||||
// Safety: the RunCommand returns the size of the command
|
||||
current = unsafe { current.add(read_size) };
|
||||
}
|
||||
|
||||
// Safety: all of the commands were just read from the pointers.
|
||||
unsafe { self.data.set_len(0) };
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CommandQueue {
|
||||
fn drop(&mut self) {
|
||||
if !self.data.is_empty() {
|
||||
println!("CommandQueue has commands but is being dropped");
|
||||
}
|
||||
|
||||
self.execute(None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Used in a system to queue up commands that will run right after this system.
|
||||
///
|
||||
/// This can be used to delay the mutation of the world until after the system is ran. These
|
||||
/// must be used if you're mutating the world inside a [`ViewState`](crate::query::ViewState).
|
||||
/// must be used if you're mutating the world inside a [`View`](crate::query::View).
|
||||
///
|
||||
/// ```nobuild
|
||||
/// fn particle_spawner_system(
|
||||
/// commands: Commands,
|
||||
/// view: ViewState<(&Campfire, &Transform)>
|
||||
/// view: View<(&Campfire, &Transform)>
|
||||
/// ) -> anyhow::Result<()> {
|
||||
/// for (campfire, pos) in view.iter() {
|
||||
/// // If you do not use commands to spawn this, the next iteration
|
||||
|
@ -66,17 +115,44 @@ impl<'a, 'b> Commands<'a, 'b> {
|
|||
|
||||
/// Add a command to the end of the command queue
|
||||
pub fn add<C: Command>(&mut self, cmd: C) {
|
||||
let cmd = Box::new(cmd);
|
||||
let run_fn = |cmd_ptr: *mut (), world: Option<&mut World>| {
|
||||
// Safety: the pointer is a type-erased pointer to the command. The pointer is read
|
||||
// then dropped out of scope, this closure will not be ran again so no use-after-free
|
||||
// will occur.
|
||||
let cmd: C = unsafe { ptr::read_unaligned(cmd_ptr.cast::<C>()) };
|
||||
match world {
|
||||
Some(world) => cmd.run(world),
|
||||
None => {} // cmd just gets dropped
|
||||
}
|
||||
|
||||
let run_fn = |cmd: Box<dyn Command>, world: &mut World| {
|
||||
let cmd = cmd.as_any_boxed()
|
||||
.downcast::<C>()
|
||||
.unwrap();
|
||||
|
||||
cmd.run(world);
|
||||
// the size of the command must be returned to increment the pointer when applying
|
||||
// the command queue.
|
||||
mem::size_of::<C>()
|
||||
};
|
||||
|
||||
self.queue.0.push_back((run_fn, cmd));
|
||||
let data = &mut self.queue.data;
|
||||
|
||||
// Reserve enough bytes from the vec to store the packed command and its run fn.
|
||||
let old_len = data.len();
|
||||
data.reserve(mem::size_of::<PackedCommand<C>>());
|
||||
|
||||
// Get a pointer to the end of the packed data. Safe since we just reserved enough memory
|
||||
// to store this command.
|
||||
let end_ptr = unsafe { data.as_mut_ptr().add(old_len) };
|
||||
|
||||
unsafe {
|
||||
// write the command and its runner into the buffer
|
||||
end_ptr.cast::<PackedCommand<C>>()
|
||||
// written unaligned to keep everything packed
|
||||
.write_unaligned(PackedCommand {
|
||||
run: run_fn,
|
||||
cmd,
|
||||
});
|
||||
|
||||
// we wrote to the vec's buffer without using its api, so we need manually
|
||||
// set the length of the vec.
|
||||
data.set_len(old_len + mem::size_of::<PackedCommand<C>>());
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn an entity into the World. See [`World::spawn`]
|
||||
|
@ -91,14 +167,8 @@ impl<'a, 'b> Commands<'a, 'b> {
|
|||
}
|
||||
|
||||
/// 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(())
|
||||
pub fn execute(&mut self, world: &mut World) {
|
||||
self.queue.execute(Some(world));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,21 +195,26 @@ impl FnArgFetcher for Commands<'_, '_> {
|
|||
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()
|
||||
cmds.execute(world);
|
||||
}
|
||||
}
|
||||
|
||||
/// A system for executing deferred commands that are stored in a [`World`] as a Resource.
|
||||
///
|
||||
/// Commands are usually added inside a system from a [`Commands`] object created just for it
|
||||
/// as an fn argument. However, there may be cases that commands cannot be added that way, so
|
||||
/// they can also be added as a resource and executed later in this system.
|
||||
pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut<Commands>) -> anyhow::Result<()> {
|
||||
commands.execute(world)?;
|
||||
commands.execute(world);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{cell::Ref, ptr::NonNull};
|
||||
use std::{cell::Ref, ptr::NonNull, sync::{atomic::{AtomicU32, Ordering}, Arc}};
|
||||
|
||||
use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, Commands, DynTypeId, World};
|
||||
use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, CommandQueue, Commands, DynTypeId, World};
|
||||
|
||||
#[test]
|
||||
fn deferred_commands() {
|
||||
|
@ -170,4 +245,28 @@ mod tests {
|
|||
let vec2: Ref<Vec2> = unsafe { col.get(3) };
|
||||
assert_eq!(vec2.clone(), spawned_vec);
|
||||
}
|
||||
|
||||
/// A test that ensures a command in a command queue will only ever run once.
|
||||
#[test]
|
||||
fn commands_only_one_exec() {
|
||||
let mut world = World::new();
|
||||
|
||||
let counter = Arc::new(AtomicU32::new(0));
|
||||
|
||||
let mut queue = CommandQueue::default();
|
||||
let mut commands = Commands::new(&mut queue, &mut world);
|
||||
|
||||
let counter_cl = counter.clone();
|
||||
commands.add(move |_world: &mut World| {
|
||||
counter_cl.fetch_add(1, Ordering::AcqRel);
|
||||
});
|
||||
|
||||
queue.execute(Some(&mut world));
|
||||
assert_eq!(1, counter.load(Ordering::Acquire));
|
||||
|
||||
queue.execute(Some(&mut world));
|
||||
// If its not one, the command somehow was executed.
|
||||
// I would be surprised it wouldn't cause some segfault but still increment the counter
|
||||
assert_eq!(1, counter.load(Ordering::Acquire));
|
||||
}
|
||||
}
|
|
@ -67,7 +67,7 @@ impl ComponentInfo {
|
|||
}
|
||||
|
||||
/// Create ComponentInfo from a type that is not known to rust
|
||||
pub fn new_unknown<D>(type_id: D, name: &str, layout: Layout) -> Self
|
||||
pub fn new_unknown<D>(name: Option<String>, type_id: D, layout: Layout) -> Self
|
||||
where
|
||||
D: Into<DynTypeId>,
|
||||
{
|
||||
|
|
|
@ -11,6 +11,24 @@ pub struct Entity {
|
|||
pub(crate) generation: u64,
|
||||
}
|
||||
|
||||
impl Entity {
|
||||
pub fn new(id: EntityId, gen: u64) -> Self {
|
||||
Self {
|
||||
id,
|
||||
generation: gen,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> EntityId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn generation(&self) -> u64 {
|
||||
self.generation
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Entities {
|
||||
pub(crate) arch_index: HashMap<EntityId, Record>,
|
||||
dead: VecDeque<Entity>,
|
||||
|
|
|
@ -30,7 +30,7 @@ pub use component::*;
|
|||
pub mod query;
|
||||
//pub use query::*;
|
||||
|
||||
mod relation;
|
||||
pub mod relation;
|
||||
pub use relation::Relation;
|
||||
|
||||
mod component_info;
|
||||
|
@ -51,6 +51,9 @@ pub mod math;
|
|||
|
||||
pub use lyra_ecs_derive::*;
|
||||
|
||||
pub use atomic_refcell::AtomicRef;
|
||||
pub use atomic_refcell::AtomicRefMut;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
|
|
|
@ -177,10 +177,10 @@ where
|
|||
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||
let col = archetype.get_column(self.type_id)
|
||||
.expect("You ignored 'can_visit_archetype'!");
|
||||
let col = NonNull::from(col);
|
||||
|
||||
|
||||
// TODO: find a way to get the component column mutable with a borrowed archetype so its tick can be updated.
|
||||
// the fetcher needs to tick the entities tick in the archetype
|
||||
let col = NonNull::from(col);
|
||||
|
||||
FetchBorrowMut {
|
||||
col,
|
||||
|
|
|
@ -5,6 +5,9 @@ use crate::{World, ComponentColumn, ComponentInfo};
|
|||
mod view;
|
||||
pub use view::*;
|
||||
|
||||
mod view_one;
|
||||
pub use view_one::*;
|
||||
|
||||
use super::Fetch;
|
||||
|
||||
/// Data that rust does not know the type of
|
||||
|
@ -19,16 +22,21 @@ impl DynamicType {
|
|||
}
|
||||
}
|
||||
|
||||
/// A struct that fetches some dynamic type.
|
||||
|
||||
/// A struct that fetches a dynamic type.
|
||||
///
|
||||
/// Currently it can only fetch from archetypes, later it will be able to fetch dynamic
|
||||
/// resources as well. Its meant to be a single Fetcher for all dynamic types.
|
||||
pub struct FetchDynamicType<'a> {
|
||||
pub col: &'a ComponentColumn,
|
||||
///
|
||||
/// # Safety
|
||||
/// Internally, this struct has a `NonNull<ComponentColumn>`. You must ensure that the column
|
||||
/// is not dropped while this is still alive.
|
||||
pub struct FetchDynamicTypeUnsafe {
|
||||
pub col: NonNull<ComponentColumn>,
|
||||
pub info: ComponentInfo,
|
||||
}
|
||||
|
||||
impl<'a> Fetch<'a> for FetchDynamicType<'a> {
|
||||
impl<'a> Fetch<'a> for FetchDynamicTypeUnsafe {
|
||||
type Item = DynamicType;
|
||||
|
||||
fn dangling() -> Self {
|
||||
|
@ -36,7 +44,7 @@ impl<'a> Fetch<'a> for FetchDynamicType<'a> {
|
|||
}
|
||||
|
||||
unsafe fn get_item(&mut self, entity: crate::ArchetypeEntityId) -> Self::Item {
|
||||
let ptr = self.col.borrow_ptr();
|
||||
let ptr = unsafe { self.col.as_ref() }.borrow_ptr();
|
||||
let ptr = NonNull::new_unchecked(ptr.as_ptr()
|
||||
.add(entity.0 as usize * self.info.layout().size()));
|
||||
|
||||
|
@ -66,13 +74,13 @@ impl QueryDynamicType {
|
|||
archetype.has_column(self.info.type_id())
|
||||
}
|
||||
|
||||
pub unsafe fn fetch<'a>(&self, _world: &'a World, _arch_id: crate::archetype::ArchetypeId, archetype: &'a crate::archetype::Archetype) -> FetchDynamicType<'a> {
|
||||
pub unsafe fn fetch<'a>(&self, _world: &'a World, _arch_id: crate::archetype::ArchetypeId, archetype: &'a crate::archetype::Archetype) -> FetchDynamicTypeUnsafe {
|
||||
let col = archetype.get_column(self.info.type_id())
|
||||
.expect("You ignored 'can_visit_archetype'!");
|
||||
|
||||
FetchDynamicType {
|
||||
col,
|
||||
info: self.info,
|
||||
FetchDynamicTypeUnsafe {
|
||||
col: NonNull::from(col),
|
||||
info: self.info
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,25 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use crate::{World, Archetype, ArchetypeEntityId, ArchetypeId, query::Fetch};
|
||||
use crate::{query::Fetch, Archetype, ArchetypeEntityId, ArchetypeId, Entity, World};
|
||||
|
||||
use super::{QueryDynamicType, FetchDynamicType, DynamicType};
|
||||
use super::{DynamicType, FetchDynamicTypeUnsafe, QueryDynamicType};
|
||||
|
||||
pub struct DynamicView<'a> {
|
||||
world: &'a World,
|
||||
queries: Vec<QueryDynamicType>
|
||||
/// Stores the state of a dynamic view.
|
||||
///
|
||||
/// See [`DynamicView`].
|
||||
///
|
||||
/// This backs [`DynamicView`] which you should probably use. The only reason you would use this
|
||||
/// instead is if you cant borrow the world when storing this type, and its iterators.
|
||||
/// [`DynamicViewState`] provides an 'iterator' of [`DynamicViewStateIter`], which requires you to
|
||||
/// provide a world borrow on each `next` of the iterator. View [`DynamicViewStateIter`] for more
|
||||
/// info.
|
||||
pub struct DynamicViewState {
|
||||
pub(crate) queries: Vec<QueryDynamicType>
|
||||
}
|
||||
|
||||
impl<'a> DynamicView<'a> {
|
||||
pub fn new(world: &'a World) -> Self {
|
||||
impl DynamicViewState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
world,
|
||||
queries: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
@ -20,45 +27,49 @@ impl<'a> DynamicView<'a> {
|
|||
pub fn push(&mut self, dyn_query: QueryDynamicType) {
|
||||
self.queries.push(dyn_query);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for DynamicView<'a> {
|
||||
type Item = Vec<DynamicType>;
|
||||
|
||||
type IntoIter = DynamicViewIter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let archetypes = self.world.archetypes.values().collect();
|
||||
|
||||
DynamicViewIter {
|
||||
world: self.world,
|
||||
pub fn into_iter(self) -> DynamicViewStateIter {
|
||||
DynamicViewStateIter {
|
||||
queries: self.queries,
|
||||
fetchers: Vec::new(),
|
||||
archetypes,
|
||||
next_archetype: 0,
|
||||
component_indices: 0..0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DynamicViewIter<'a> {
|
||||
pub world: &'a World,
|
||||
pub queries: Vec<QueryDynamicType>,
|
||||
pub fetchers: Vec<FetchDynamicType<'a>>,
|
||||
pub archetypes: Vec<&'a Archetype>,
|
||||
pub next_archetype: usize,
|
||||
pub component_indices: Range<u64>,
|
||||
pub struct DynamicViewItem {
|
||||
pub row: Vec<DynamicType>,
|
||||
pub entity: Entity,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DynamicViewIter<'a> {
|
||||
type Item = Vec<DynamicType>;
|
||||
/// A view iterator on dynamic types.
|
||||
///
|
||||
/// You will likely want to use [`DynamicViewIter`] unless you need to store the iterator
|
||||
/// without also borrowing from the world. [`DynamicViewStateIter`] doesn't
|
||||
/// actually implement [`Iterator`] since it requires a `&World` to be provided to it
|
||||
/// each time `next` is ran (see [`DynamicViewStateIter::next`]).
|
||||
pub struct DynamicViewStateIter {
|
||||
pub queries: Vec<QueryDynamicType>,
|
||||
fetchers: Vec<FetchDynamicTypeUnsafe>,
|
||||
next_archetype: usize,
|
||||
component_indices: Range<u64>,
|
||||
}
|
||||
|
||||
impl DynamicViewStateIter {
|
||||
pub fn next(&mut self, world: &World) -> Option<(Entity, Vec<DynamicType>)> {
|
||||
let archetypes = world.archetypes.values().collect::<Vec<_>>();
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if let Some(entity_index) = self.component_indices.next() {
|
||||
let entity = {
|
||||
let arch_id = self.next_archetype - 1;
|
||||
let arch = unsafe { archetypes.get_unchecked(arch_id) };
|
||||
arch.entity_at_index(ArchetypeEntityId(entity_index)).unwrap()
|
||||
};
|
||||
|
||||
let mut fetch_res = vec![];
|
||||
|
||||
//let fetcher = self.fetcher.as_mut().unwrap();
|
||||
for fetcher in self.fetchers.iter_mut() {
|
||||
let entity_index = ArchetypeEntityId(entity_index);
|
||||
if !fetcher.can_visit_item(entity_index) {
|
||||
|
@ -73,15 +84,15 @@ impl<'a> Iterator for DynamicViewIter<'a> {
|
|||
continue;
|
||||
}
|
||||
|
||||
return Some(fetch_res);
|
||||
return Some((entity, fetch_res));
|
||||
} else {
|
||||
if self.next_archetype >= self.archetypes.len() {
|
||||
if self.next_archetype >= archetypes.len() {
|
||||
return None; // ran out of archetypes to go through
|
||||
}
|
||||
|
||||
let arch_id = self.next_archetype;
|
||||
self.next_archetype += 1;
|
||||
let arch = unsafe { self.archetypes.get_unchecked(arch_id) };
|
||||
let arch = unsafe { archetypes.get_unchecked(arch_id) };
|
||||
|
||||
if arch.entity_ids.is_empty() {
|
||||
continue;
|
||||
|
@ -92,7 +103,7 @@ impl<'a> Iterator for DynamicViewIter<'a> {
|
|||
}
|
||||
|
||||
self.fetchers = self.queries.iter()
|
||||
.map(|q| unsafe { q.fetch(self.world, ArchetypeId(arch_id as u64), arch) } )
|
||||
.map(|q| unsafe { q.fetch(world, ArchetypeId(arch_id as u64), arch) } )
|
||||
.collect();
|
||||
self.component_indices = 0..arch.entity_ids.len() as u64;
|
||||
}
|
||||
|
@ -100,18 +111,106 @@ impl<'a> Iterator for DynamicViewIter<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// A view of dynamic types (types that are not known to Rust).
|
||||
///
|
||||
/// This view gives you the ability to iterate over types that are unknown to Rust, which we call
|
||||
/// dynamic types. This is great for embedding with a scripting language (*cough* *cough* WASM)
|
||||
/// since Rust doesn't actually need to know the types of what its iterating over.
|
||||
pub struct DynamicView<'a> {
|
||||
world: &'a World,
|
||||
inner: DynamicViewState,
|
||||
}
|
||||
|
||||
impl<'a> DynamicView<'a> {
|
||||
pub fn new(world: &'a World) -> Self {
|
||||
Self {
|
||||
world,
|
||||
inner: DynamicViewState::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, dyn_query: QueryDynamicType) {
|
||||
self.inner.queries.push(dyn_query);
|
||||
}
|
||||
}
|
||||
|
||||
/// A view iterator on dynamic types.
|
||||
///
|
||||
/// This view gives you the ability to iterate over types that are completely unknown to Rust.
|
||||
/// This works great for a embedding with a scripting language (*cough* *cough* WASM) since
|
||||
/// Rust doesn't actually need to know the types of what its iterating over.
|
||||
impl<'a> IntoIterator for DynamicView<'a> {
|
||||
type Item = (Entity, Vec<DynamicType>);
|
||||
|
||||
type IntoIter = DynamicViewIter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let archetypes = self.world.archetypes.values().collect();
|
||||
|
||||
DynamicViewIter {
|
||||
world: self.world,
|
||||
archetypes,
|
||||
inner: self.inner.into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DynamicViewIter<'a> {
|
||||
pub world: &'a World,
|
||||
pub archetypes: Vec<&'a Archetype>,
|
||||
inner: DynamicViewStateIter,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for DynamicViewIter<'a> {
|
||||
type Item = (Entity, Vec<DynamicType>);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next(&self.world)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{alloc::Layout, ptr::NonNull};
|
||||
|
||||
use crate::{World, ComponentInfo, DynTypeId, DynamicBundle, query::dynamic::QueryDynamicType};
|
||||
|
||||
use super::DynamicView;
|
||||
use super::{DynamicView, DynamicViewState};
|
||||
|
||||
#[test]
|
||||
fn single_dynamic_view_state() {
|
||||
let comp_layout = Layout::new::<u32>();
|
||||
let comp_info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), comp_layout);
|
||||
|
||||
let mut dynamic_bundle = DynamicBundle::default();
|
||||
let comp = 50u32;
|
||||
let ptr = NonNull::from(&comp).cast::<u8>();
|
||||
dynamic_bundle.push_unknown(ptr, comp_info.clone());
|
||||
|
||||
let mut world = World::new();
|
||||
world.spawn(dynamic_bundle);
|
||||
|
||||
let query = QueryDynamicType::from_info(comp_info);
|
||||
let mut view = DynamicViewState::new();
|
||||
view.push(query);
|
||||
|
||||
let mut view_iter = view.into_iter();
|
||||
while let Some((_e, view_row)) = view_iter.next(&world) {
|
||||
assert_eq!(view_row.len(), 1);
|
||||
|
||||
let mut row_iter = view_row.row.iter();
|
||||
|
||||
let dynamic_type = row_iter.next().unwrap();
|
||||
|
||||
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
|
||||
assert_eq!(*component_data, 50);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_dynamic_view() {
|
||||
let comp_layout = Layout::new::<u32>();
|
||||
let comp_info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", comp_layout);
|
||||
let comp_info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), comp_layout);
|
||||
|
||||
let mut dynamic_bundle = DynamicBundle::default();
|
||||
let comp = 50u32;
|
||||
|
@ -125,10 +224,10 @@ mod tests {
|
|||
let mut view = DynamicView::new(&world);
|
||||
view.push(query);
|
||||
|
||||
for view_row in view.into_iter() {
|
||||
for (_e, view_row) in view.into_iter() {
|
||||
assert_eq!(view_row.len(), 1);
|
||||
|
||||
let mut row_iter = view_row.iter();
|
||||
let mut row_iter = view_row.row.iter();
|
||||
|
||||
let dynamic_type = row_iter.next().unwrap();
|
||||
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
use crate::{query::Fetch, Entity, World};
|
||||
|
||||
use super::{DynamicType, FetchDynamicTypeUnsafe, QueryDynamicType};
|
||||
|
||||
/// A view of dynamic types (types that are not known to Rust).
|
||||
///
|
||||
/// This view gives you the ability to iterate over types that are unknown to Rust, which we call
|
||||
/// dynamic types. This is great for embedding with a scripting language (*cough* *cough* WASM)
|
||||
/// since Rust doesn't actually need to know the types of what its iterating over.
|
||||
pub struct DynamicViewOne<'a> {
|
||||
world: &'a World,
|
||||
pub entity: Entity,
|
||||
pub queries: Vec<QueryDynamicType>
|
||||
}
|
||||
|
||||
impl<'a> DynamicViewOne<'a> {
|
||||
pub fn new(world: &'a World, entity: Entity) -> Self {
|
||||
Self {
|
||||
world,
|
||||
entity,
|
||||
queries: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`DynamicViewOne`] with queries.
|
||||
pub fn new_with(world: &'a World, entity: Entity, queries: Vec<QueryDynamicType>) -> Self {
|
||||
Self {
|
||||
world,
|
||||
entity,
|
||||
queries
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(self) -> Option<Vec<DynamicType>> {
|
||||
let arch = self.world.entity_archetype(self.entity)?;
|
||||
let aid = arch.entity_indexes().get(&self.entity)?;
|
||||
|
||||
// get all fetchers for the queries
|
||||
let mut fetchers: Vec<FetchDynamicTypeUnsafe> = self.queries.iter()
|
||||
.map(|q| unsafe { q.fetch(self.world, arch.id(), arch) } )
|
||||
.collect();
|
||||
|
||||
let mut fetch_res = vec![];
|
||||
for fetcher in fetchers.iter_mut() {
|
||||
if !fetcher.can_visit_item(*aid) {
|
||||
return None;
|
||||
} else {
|
||||
let i = unsafe { fetcher.get_item(*aid) };
|
||||
fetch_res.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
if fetch_res.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(fetch_res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{alloc::Layout, ptr::NonNull};
|
||||
|
||||
use crate::{World, ComponentInfo, DynTypeId, DynamicBundle, query::dynamic::QueryDynamicType};
|
||||
|
||||
use super::DynamicViewOne;
|
||||
|
||||
#[test]
|
||||
fn single_dynamic_view_one_state() {
|
||||
let comp_layout = Layout::new::<u32>();
|
||||
let comp_info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), comp_layout);
|
||||
|
||||
let mut dynamic_bundle = DynamicBundle::default();
|
||||
let comp = 50u32;
|
||||
let ptr = NonNull::from(&comp).cast::<u8>();
|
||||
dynamic_bundle.push_unknown(ptr, comp_info.clone());
|
||||
|
||||
let mut world = World::new();
|
||||
let e = world.spawn(dynamic_bundle);
|
||||
|
||||
let query = QueryDynamicType::from_info(comp_info);
|
||||
let view = DynamicViewOne::new_with(&world, e, vec![query]);
|
||||
|
||||
let view_row = view.get()
|
||||
.expect("failed to get entity row");
|
||||
assert_eq!(view_row.len(), 1);
|
||||
|
||||
let mut row_iter = view_row.iter();
|
||||
let dynamic_type = row_iter.next().unwrap();
|
||||
|
||||
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
|
||||
assert_eq!(*component_data, 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_dynamic_view_one() {
|
||||
let comp_layout = Layout::new::<u32>();
|
||||
let comp_info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), comp_layout);
|
||||
|
||||
let mut dynamic_bundle = DynamicBundle::default();
|
||||
let comp = 50u32;
|
||||
let ptr = NonNull::from(&comp).cast::<u8>();
|
||||
dynamic_bundle.push_unknown(ptr, comp_info.clone());
|
||||
|
||||
let mut world = World::new();
|
||||
let e = world.spawn(dynamic_bundle);
|
||||
|
||||
let query = QueryDynamicType::from_info(comp_info);
|
||||
let view = DynamicViewOne::new_with(&world, e, vec![query]);
|
||||
|
||||
let view_row = view.get()
|
||||
.expect("failed to get entity row");
|
||||
assert_eq!(view_row.len(), 1);
|
||||
|
||||
let mut row_iter = view_row.iter();
|
||||
|
||||
let dynamic_type = row_iter.next().unwrap();
|
||||
|
||||
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
|
||||
assert_eq!(*component_data, 50);
|
||||
}
|
||||
}
|
|
@ -2,21 +2,20 @@ use crate::{archetype::Archetype, World, Entity};
|
|||
|
||||
use super::{Fetch, Query, AsQuery};
|
||||
|
||||
pub struct EntitiesFetch {
|
||||
entities: Vec<Entity>,
|
||||
pub struct EntitiesFetch<'a> {
|
||||
archetype_entities: Option<&'a [Entity]>,
|
||||
}
|
||||
|
||||
impl<'a> Fetch<'a> for EntitiesFetch {
|
||||
impl<'a> Fetch<'a> for EntitiesFetch<'a> {
|
||||
type Item = Entity;
|
||||
|
||||
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
|
||||
let e = *self.entities.get_unchecked(entity.0 as usize);
|
||||
e
|
||||
self.archetype_entities.unwrap()[entity.0 as usize]
|
||||
}
|
||||
|
||||
fn dangling() -> Self {
|
||||
Self {
|
||||
entities: vec![],
|
||||
archetype_entities: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +26,7 @@ pub struct Entities;
|
|||
impl Query for Entities {
|
||||
type Item<'a> = Entity;
|
||||
|
||||
type Fetch<'a> = EntitiesFetch;
|
||||
type Fetch<'a> = EntitiesFetch<'a>;
|
||||
|
||||
fn new() -> Self {
|
||||
Entities
|
||||
|
@ -41,7 +40,7 @@ impl Query for Entities {
|
|||
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||
let _ = tick; // ignore unused warnings
|
||||
EntitiesFetch {
|
||||
entities: archetype.entity_ids.keys().cloned().collect::<Vec<Entity>>(),
|
||||
archetype_entities: Some(&archetype.entities),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{query::{AsQuery, Query}, Archetype, Component, DynTypeId, World};
|
||||
|
||||
/// A filter query that fetches when the entity has the component `C`.
|
||||
///
|
||||
/// This does not return a reference to the component, it returns `()` if the entity has
|
||||
/// the component. This query is great when its used with [`Or`](super::Or).
|
||||
#[derive(Default)]
|
||||
pub struct Has<C: Component> {
|
||||
_marker: PhantomData<C>
|
||||
}
|
||||
|
||||
impl<C: Component> Copy for Has<C> {}
|
||||
|
||||
impl<C: Component> Clone for Has<C> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { _marker: self._marker.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component> Query for Has<C> {
|
||||
type Item<'a> = ();
|
||||
|
||||
type Fetch<'a> = ();
|
||||
|
||||
fn new() -> Self {
|
||||
Has {
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
fn can_visit_archetype(&self, archetype: &Archetype) -> bool {
|
||||
archetype.has_column(DynTypeId::of::<C>())
|
||||
}
|
||||
|
||||
unsafe fn fetch<'a>(&self, _world: &'a World, _: &'a Archetype, _: crate::Tick) -> Self::Fetch<'a> {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component> AsQuery for Has<C> {
|
||||
type Query = Self;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
mod has;
|
||||
pub use has::*;
|
||||
|
||||
mod or;
|
||||
pub use or::*;
|
||||
|
||||
mod not;
|
||||
pub use not::*;
|
|
@ -0,0 +1,44 @@
|
|||
use crate::{query::{AsQuery, Query}, Archetype, World};
|
||||
|
||||
/// A filter query that fetches the inverse of `Q`.
|
||||
///
|
||||
/// This means that entities that `Q` fetches are skipped, and entities that
|
||||
/// `Q` does not fetch are not skipped.
|
||||
///
|
||||
/// ```nobuild
|
||||
/// // Iterate over entities that has a transform, and are not the origin of a `ChildOf` relationship.
|
||||
/// for (en, pos, _) in world
|
||||
/// .view::<(Entities, &Transform, Not<Has<RelationOriginComponent<ChildOf>>>)>()
|
||||
/// .iter()
|
||||
/// {
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Default, Copy, Clone)]
|
||||
pub struct Not<Q: Query> {
|
||||
query: Q,
|
||||
}
|
||||
|
||||
impl<Q: Query> Query for Not<Q> {
|
||||
type Item<'a> = ();
|
||||
|
||||
type Fetch<'a> = ();
|
||||
|
||||
fn new() -> Self {
|
||||
Not {
|
||||
query: Q::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn can_visit_archetype(&self, archetype: &Archetype) -> bool {
|
||||
!self.query.can_visit_archetype(archetype)
|
||||
}
|
||||
|
||||
unsafe fn fetch<'a>(&self, _world: &'a World, _: &'a Archetype, _: crate::Tick) -> Self::Fetch<'a> {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Query> AsQuery for Not<Q> {
|
||||
type Query = Self;
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
use crate::{query::{AsQuery, Fetch, Query}, Archetype, World};
|
||||
|
||||
pub struct OrFetch<'a, Q1: Query, Q2: Query> {
|
||||
left: Option<Q1::Fetch<'a>>,
|
||||
right: Option<Q2::Fetch<'a>>,
|
||||
}
|
||||
|
||||
impl<'a, Q1: Query, Q2: Query> Fetch<'a> for OrFetch<'a, Q1, Q2> {
|
||||
type Item = (Option<Q1::Item<'a>>, Option<Q2::Item<'a>>);
|
||||
|
||||
fn dangling() -> Self {
|
||||
Self {
|
||||
left: None,
|
||||
right: None,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_item(&mut self, entity: crate::ArchetypeEntityId) -> Self::Item {
|
||||
let mut res = (None, None);
|
||||
|
||||
if let Some(left) = self.left.as_mut() {
|
||||
let i = left.get_item(entity);
|
||||
res.0 = Some(i);
|
||||
}
|
||||
|
||||
if let Some(right) = self.right.as_mut() {
|
||||
let i = right.get_item(entity);
|
||||
res.1 = Some(i);
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// A filter query returning when either `Q1` or `Q2` returns.
|
||||
///
|
||||
/// This checks if `Q1` can fetch before checking `Q2`.
|
||||
///
|
||||
/// ```nobuild
|
||||
/// for (en, pos, _) in world
|
||||
/// .view::<(Entities, &Transform, Or<Has<Mesh>, Has<Scene>>)>()
|
||||
/// .iter()
|
||||
/// {
|
||||
/// // do some things with the position of the entities
|
||||
///
|
||||
/// // now handle do things with the Mesh or Scene that the entity could have
|
||||
/// if let Some(mesh) = world.view_one::<&Mesh>(en).get() {
|
||||
/// // do mesh things
|
||||
/// }
|
||||
///
|
||||
/// if let Some(scene) = world.view_one::<&Scene>(en).get() {
|
||||
/// // do scene things
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct Or<Q1: AsQuery, Q2: AsQuery> {
|
||||
left: Q1::Query,
|
||||
right: Q2::Query,
|
||||
can_visit_left: bool,
|
||||
can_visit_right: bool,
|
||||
}
|
||||
|
||||
impl<Q1: AsQuery, Q2: AsQuery> Copy for Or<Q1, Q2> {}
|
||||
|
||||
impl<Q1: AsQuery, Q2: AsQuery> Clone for Or<Q1, Q2> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
left: self.left.clone(),
|
||||
right: self.right.clone(),
|
||||
can_visit_left: self.can_visit_left,
|
||||
can_visit_right: self.can_visit_right,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q1: AsQuery, Q2: AsQuery> Query for Or<Q1, Q2> {
|
||||
type Item<'a> = (Option<<Q1::Query as Query>::Item<'a>>, Option<<Q2::Query as Query>::Item<'a>>);
|
||||
|
||||
type Fetch<'a> = OrFetch<'a, Q1::Query, Q2::Query>;
|
||||
|
||||
fn new() -> Self {
|
||||
Or {
|
||||
left: Q1::Query::new(),
|
||||
right: Q2::Query::new(),
|
||||
can_visit_left: false,
|
||||
can_visit_right: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn can_visit_archetype(&self, archetype: &Archetype) -> bool {
|
||||
self.left.can_visit_archetype(archetype) || self.right.can_visit_archetype(archetype)
|
||||
}
|
||||
|
||||
unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||
let mut f = OrFetch::<Q1::Query, Q2::Query>::dangling();
|
||||
|
||||
// TODO: store the result of Self::can_visit_archetype so this isn't ran twice
|
||||
if self.left.can_visit_archetype(archetype) {
|
||||
f.left = Some(self.left.fetch(world, archetype, tick));
|
||||
}
|
||||
|
||||
if self.right.can_visit_archetype(archetype) {
|
||||
f.right = Some(self.right.fetch(world, archetype, tick));
|
||||
}
|
||||
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q1: AsQuery, Q2: AsQuery> AsQuery for Or<Q1, Q2> {
|
||||
type Query = Self;
|
||||
}
|
|
@ -27,8 +27,14 @@ mod world;
|
|||
#[allow(unused_imports)]
|
||||
pub use world::*;
|
||||
|
||||
mod optional;
|
||||
#[allow(unused_imports)]
|
||||
pub use optional::*;
|
||||
|
||||
pub mod dynamic;
|
||||
|
||||
pub mod filter;
|
||||
|
||||
/// A [`Fetch`]er implementation gets data out of an archetype.
|
||||
pub trait Fetch<'a> {
|
||||
/// The type that this Fetch yields
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
use crate::{Archetype, World};
|
||||
|
||||
use super::{AsQuery, Fetch, Query};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OptionalFetcher<'a, Q: AsQuery> {
|
||||
fetcher: Option<<Q::Query as Query>::Fetch<'a>>,
|
||||
}
|
||||
|
||||
impl<'a, Q: AsQuery> Fetch<'a> for OptionalFetcher<'a, Q> {
|
||||
type Item = Option<<Q::Query as Query>::Item<'a>>;
|
||||
|
||||
fn dangling() -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
unsafe fn get_item(&mut self, entity: crate::ArchetypeEntityId) -> Self::Item {
|
||||
self.fetcher.as_mut()
|
||||
.map(|f| f.get_item(entity))
|
||||
}
|
||||
|
||||
fn can_visit_item(&mut self, entity: crate::ArchetypeEntityId) -> bool {
|
||||
self.fetcher.as_mut()
|
||||
.map(|f| f.can_visit_item(entity))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Optional<Q: AsQuery> {
|
||||
query: Q::Query,
|
||||
}
|
||||
|
||||
impl<Q: AsQuery> Copy for Optional<Q> { }
|
||||
|
||||
impl<Q: AsQuery> Clone for Optional<Q> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { query: self.query.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: AsQuery> Query for Optional<Q> {
|
||||
type Item<'a> = Option<<Q::Query as Query>::Item<'a>>;
|
||||
|
||||
type Fetch<'a> = OptionalFetcher<'a, Q>;
|
||||
|
||||
fn new() -> Self {
|
||||
Optional {
|
||||
query: Q::Query::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn can_visit_archetype(&self, _: &Archetype) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
unsafe fn fetch<'a>(&self, world: &'a World, arch: &'a Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||
let fetcher = if self.query.can_visit_archetype(arch) {
|
||||
Some(self.query.fetch(world, arch, tick))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
OptionalFetcher {
|
||||
fetcher,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: AsQuery> AsQuery for Optional<Q> {
|
||||
type Query = Self;
|
||||
}
|
||||
|
||||
impl<Q: AsQuery> AsQuery for Option<Q> {
|
||||
type Query = Optional<Q>;
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
use std::{marker::PhantomData, cell::{Ref, RefMut}};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
||||
|
||||
use crate::{World, resource::ResourceObject};
|
||||
|
||||
|
@ -9,7 +11,7 @@ pub struct FetchResource<'a, T> {
|
|||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + 'static> Fetch<'a> for FetchResource<'a, T> {
|
||||
impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResource<'a, T> {
|
||||
type Item = Res<'a, T>;
|
||||
|
||||
fn dangling() -> Self {
|
||||
|
@ -79,7 +81,7 @@ impl<R: ResourceObject> AsQuery for QueryResource<R> {
|
|||
}
|
||||
|
||||
/// A struct used for querying resources from the World.
|
||||
pub struct Res<'a, T>(pub(crate) Ref<'a, T>);
|
||||
pub struct Res<'a, T: ResourceObject>(pub(crate) AtomicRef<'a, T>);
|
||||
|
||||
impl<'a, T: ResourceObject> std::ops::Deref for Res<'a, T> {
|
||||
type Target = T;
|
||||
|
@ -98,7 +100,7 @@ pub struct FetchResourceMut<'a, T> {
|
|||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'a + 'static> Fetch<'a> for FetchResourceMut<'a, T> {
|
||||
impl<'a, T: ResourceObject + 'a> Fetch<'a> for FetchResourceMut<'a, T> {
|
||||
type Item = ResMut<'a, T>;
|
||||
|
||||
fn dangling() -> Self {
|
||||
|
@ -167,7 +169,7 @@ impl<R: ResourceObject> AsQuery for QueryResourceMut<R> {
|
|||
}
|
||||
|
||||
/// A struct used for querying resources from the World.
|
||||
pub struct ResMut<'a, T>(pub(crate) RefMut<'a, T>);
|
||||
pub struct ResMut<'a, T: ResourceObject>(pub(crate) AtomicRefMut<'a, T>);
|
||||
|
||||
impl<'a, T: ResourceObject> std::ops::Deref for ResMut<'a, T> {
|
||||
type Target = T;
|
||||
|
|
|
@ -90,7 +90,7 @@ where
|
|||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if Q::ALWAYS_FETCHES && F::ALWAYS_FETCHES {
|
||||
if Q::ALWAYS_FETCHES {
|
||||
// only fetch this query once.
|
||||
// fetcher gets set to Some after this `next` call.
|
||||
if self.fetcher.is_none() {
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
use super::Relation;
|
||||
|
||||
// TODO: Delete child entities when the parent is deleted
|
||||
pub struct ChildOf;
|
||||
|
||||
impl Relation for ChildOf {
|
||||
|
||||
}
|
|
@ -10,19 +10,20 @@ use crate::lyra_engine;
|
|||
use crate::World;
|
||||
|
||||
mod relates_to;
|
||||
#[doc(hidden)]
|
||||
pub use relates_to::*;
|
||||
|
||||
mod relate_pair;
|
||||
#[doc(hidden)]
|
||||
pub use relate_pair::*;
|
||||
|
||||
mod child_of;
|
||||
pub use child_of::*;
|
||||
|
||||
#[allow(unused_variables)]
|
||||
pub trait Relation: 'static {
|
||||
/// called when a relation of this type is set on a target
|
||||
fn relation_add(&self, origin: Entity, target: Entity) { }
|
||||
fn relation_add(&mut self, world: &mut World, origin: Entity, target: Entity) { }
|
||||
/// called when a relation is removed
|
||||
fn relation_remove(&self, origin: Entity, target: Entity) { }
|
||||
fn relation_remove(&mut self, world: &mut World, origin: Entity, target: Entity) { }
|
||||
}
|
||||
|
||||
/// A component that stores the target of a relation.
|
||||
|
@ -31,10 +32,16 @@ pub trait Relation: 'static {
|
|||
/// entities that the relation targets.
|
||||
#[derive(Component)]
|
||||
pub struct RelationOriginComponent<R: Relation> {
|
||||
pub(crate) relation: R,
|
||||
pub relation: R,
|
||||
target: Entity,
|
||||
}
|
||||
|
||||
impl<R: Relation> RelationOriginComponent<R> {
|
||||
pub fn target(&self) -> Entity {
|
||||
self.target
|
||||
}
|
||||
}
|
||||
|
||||
/// A component that stores the origin of a relation.
|
||||
///
|
||||
/// This component is on the target of the relation and can be used to find the
|
||||
|
@ -79,12 +86,12 @@ impl World {
|
|||
};
|
||||
self.insert(target, comp);
|
||||
|
||||
let comp = RelationOriginComponent {
|
||||
let mut comp = RelationOriginComponent {
|
||||
relation,
|
||||
target,
|
||||
};
|
||||
|
||||
comp.relation.relation_add(origin, target);
|
||||
comp.relation.relation_add(self, origin, target);
|
||||
self.insert(origin, comp);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{any::{Any, TypeId}, cell::Ref, marker::PhantomData};
|
||||
use std::{any::TypeId, cell::Ref, marker::PhantomData};
|
||||
|
||||
use crate::{query::{AsQuery, Fetch, Query}, Archetype, ComponentColumn, Entity, World};
|
||||
|
||||
|
@ -69,7 +69,7 @@ where
|
|||
|
||||
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||
let _ = tick;
|
||||
let col = archetype.get_column(self.type_id())
|
||||
let col = archetype.get_column(TypeId::of::<RelationOriginComponent<R>>())
|
||||
.expect("You ignored 'can_visit_archetype'!");
|
||||
|
||||
FetchRelatePair {
|
||||
|
@ -83,8 +83,10 @@ where
|
|||
/// A query that fetches the origin, and target of a relation of type `R`.
|
||||
///
|
||||
/// It provides it as a tuple in the following format: `(origin, relation, target)`.
|
||||
/// Similar to [`RelatesTo`](super::RelatesTo), you can use [`ViewState::relate_pair`] to get a view that fetches the
|
||||
/// pair, or unlike [`RelatesTo`](super::RelatesTo), you can do the common procedure of using [`World::view`].
|
||||
/// Similar to [`RelatesTo`](super::RelatesTo), you can use
|
||||
/// [`ViewState::relate_pair`](crate::relation::ViewState::relate_pair) to get a view that
|
||||
/// fetches the pair, or unlike [`RelatesTo`](super::RelatesTo), you can do the common
|
||||
/// procedure of using [`World::view`].
|
||||
pub struct RelatePair<R: Relation> {
|
||||
_marker: PhantomData<R>,
|
||||
}
|
||||
|
|
|
@ -1,26 +1,46 @@
|
|||
use std::{any::{TypeId, Any}, cell::{RefCell, Ref, RefMut}};
|
||||
use std::{any::{Any, TypeId}, sync::Arc};
|
||||
|
||||
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
|
||||
|
||||
use crate::{Tick, TickTracker};
|
||||
|
||||
/// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually.
|
||||
pub trait ResourceObject: 'static {}
|
||||
impl<T: 'static> ResourceObject for T {}
|
||||
pub trait ResourceObject: Send + Sync + Any {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
impl<T: Send + Sync + Any> ResourceObject for T {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A type erased storage for a Resource.
|
||||
#[derive(Clone)]
|
||||
pub struct ResourceData {
|
||||
pub(crate) data: Box<RefCell<dyn Any>>,
|
||||
pub(crate) data: Arc<AtomicRefCell<dyn ResourceObject>>,
|
||||
type_id: TypeId,
|
||||
// use a tick tracker which has interior mutability
|
||||
pub(crate) tick: TickTracker,
|
||||
}
|
||||
|
||||
impl ResourceData {
|
||||
pub fn new<T: Any>(data: T) -> Self {
|
||||
pub fn new<T: ResourceObject>(data: T, tick: Tick) -> Self {
|
||||
|
||||
Self {
|
||||
data: Box::new(RefCell::new(data)),
|
||||
data: Arc::new(AtomicRefCell::new(data)),
|
||||
type_id: TypeId::of::<T>(),
|
||||
tick: TickTracker::from(*tick),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating whether or not `T`` is of the same type of the Resource
|
||||
pub fn is<T: 'static>(&self) -> bool {
|
||||
pub fn is<T: ResourceObject>(&self) -> bool {
|
||||
self.type_id == TypeId::of::<T>()
|
||||
}
|
||||
|
||||
|
@ -30,8 +50,8 @@ impl ResourceData {
|
|||
///
|
||||
/// * If the data is already borrowed mutably, this will panic.
|
||||
/// * If the type of `T` is not the same as the resource type.
|
||||
pub fn get<T: 'static>(&self) -> Ref<T> {
|
||||
Ref::map(self.data.borrow(), |a| a.downcast_ref().unwrap())
|
||||
pub fn get<T: ResourceObject>(&self) -> AtomicRef<T> {
|
||||
AtomicRef::map(self.data.borrow(), |a| a.as_any().downcast_ref().unwrap())
|
||||
}
|
||||
|
||||
/// Mutably borrow the data inside of the resource.
|
||||
|
@ -40,8 +60,8 @@ impl ResourceData {
|
|||
///
|
||||
/// * If the data is already borrowed mutably, this will panic.
|
||||
/// * If the type of `T` is not the same as the resource type.
|
||||
pub fn get_mut<T: 'static>(&self) -> RefMut<T> {
|
||||
RefMut::map(self.data.borrow_mut(), |a| a.downcast_mut().unwrap())
|
||||
pub fn get_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
|
||||
AtomicRefMut::map(self.data.borrow_mut(), |a| a.as_any_mut().downcast_mut().unwrap())
|
||||
}
|
||||
|
||||
/// Borrow the data inside of the resource.
|
||||
|
@ -49,10 +69,9 @@ impl ResourceData {
|
|||
/// # Panics
|
||||
///
|
||||
/// * If the type of `T` is not the same as the resource type.
|
||||
pub fn try_get<T: 'static>(&self) -> Option<Ref<T>> {
|
||||
|
||||
pub fn try_get<T: ResourceObject>(&self) -> Option<AtomicRef<T>> {
|
||||
self.data.try_borrow()
|
||||
.map(|r| Ref::map(r, |a| a.downcast_ref().unwrap()))
|
||||
.map(|r| AtomicRef::map(r, |a| a.as_any().downcast_ref().unwrap()))
|
||||
.ok()
|
||||
}
|
||||
|
||||
|
@ -61,9 +80,13 @@ impl ResourceData {
|
|||
/// # Panics
|
||||
///
|
||||
/// * If the type of `T` is not the same as the resource type.
|
||||
pub fn try_get_mut<T: 'static>(&self) -> Option<RefMut<T>> {
|
||||
pub fn try_get_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
|
||||
self.data.try_borrow_mut()
|
||||
.map(|r| RefMut::map(r, |a| a.downcast_mut().unwrap()))
|
||||
.map(|r| AtomicRefMut::map(r, |a| a.as_any_mut().downcast_mut().unwrap()))
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn changed(&self, tick: Tick) -> bool {
|
||||
self.tick.current() >= tick
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
use lyra_ecs::World;
|
||||
use tracing::{debug_span, instrument};
|
||||
|
||||
use crate::Access;
|
||||
|
||||
use super::{System, Criteria, IntoSystem};
|
||||
use super::{Criteria, GraphExecutorError, IntoSystem, System};
|
||||
|
||||
/// A system that executes a batch of systems in order that they were given.
|
||||
/// You can optionally add criteria that must pass before the systems are
|
||||
|
@ -12,6 +13,7 @@ pub struct BatchedSystem {
|
|||
systems: Vec<Box<dyn System>>,
|
||||
criteria: Vec<Box<dyn Criteria>>,
|
||||
criteria_checks: u32,
|
||||
did_run: bool,
|
||||
}
|
||||
|
||||
impl BatchedSystem {
|
||||
|
@ -46,11 +48,15 @@ impl System for BatchedSystem {
|
|||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, world))]
|
||||
fn execute(&mut self, world: std::ptr::NonNull<World>) -> anyhow::Result<()> {
|
||||
let mut can_run = true;
|
||||
let mut check_again = false;
|
||||
|
||||
for criteria in self.criteria.iter_mut() {
|
||||
let crit_span = debug_span!("criteria");
|
||||
let _e = crit_span.enter();
|
||||
|
||||
match criteria.can_run(world, self.criteria_checks) {
|
||||
super::CriteriaSchedule::Yes => {},
|
||||
super::CriteriaSchedule::No => can_run = false,
|
||||
|
@ -65,9 +71,26 @@ impl System for BatchedSystem {
|
|||
}
|
||||
|
||||
if can_run {
|
||||
for system in self.systems.iter_mut() {
|
||||
system.execute(world)?;
|
||||
for criteria in self.criteria.iter_mut() {
|
||||
criteria.modify_world(world);
|
||||
}
|
||||
|
||||
for (idx, system) in self.systems.iter_mut().enumerate() {
|
||||
let span = debug_span!("batch", system=idx);
|
||||
let _e = span.enter();
|
||||
|
||||
system.execute(world)?;
|
||||
|
||||
/* let deferred_span = debug_span!("deferred_exec");
|
||||
let _e = deferred_span.enter();
|
||||
|
||||
if let Err(e) = system.execute_deferred(world)
|
||||
.map_err(|e| GraphExecutorError::Command(e)) {
|
||||
return Err(e.into());
|
||||
} */
|
||||
}
|
||||
|
||||
self.did_run = true;
|
||||
}
|
||||
|
||||
if check_again {
|
||||
|
@ -79,9 +102,26 @@ impl System for BatchedSystem {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_deferred(&mut self, _: std::ptr::NonNull<World>) -> anyhow::Result<()> {
|
||||
todo!()
|
||||
|
||||
#[instrument(skip(self, world))]
|
||||
fn execute_deferred(&mut self, world: std::ptr::NonNull<World>) -> anyhow::Result<()> {
|
||||
if self.did_run {
|
||||
for (idx, system) in self.systems.iter_mut().enumerate() {
|
||||
let span = debug_span!("batch", system=idx);
|
||||
let _e = span.enter();
|
||||
|
||||
system.execute_deferred(world)
|
||||
.map_err(|e| GraphExecutorError::Command(e))?;
|
||||
}
|
||||
|
||||
for criteria in self.criteria.iter_mut() {
|
||||
criteria.undo_world_modifications(world);
|
||||
}
|
||||
}
|
||||
|
||||
self.did_run = false;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,13 +26,17 @@ pub trait Criteria {
|
|||
/// * `world` - The ecs world.
|
||||
/// * `check_count` - The amount of times the Criteria has been checked this tick.
|
||||
fn can_run(&mut self, world: NonNull<World>, check_count: u32) -> CriteriaSchedule;
|
||||
}
|
||||
|
||||
impl<F> Criteria for F
|
||||
where F: FnMut(&mut World, u32) -> CriteriaSchedule
|
||||
{
|
||||
fn can_run(&mut self, mut world: NonNull<World>, check_count: u32) -> CriteriaSchedule {
|
||||
let world_mut = unsafe { world.as_mut() };
|
||||
self(world_mut, check_count)
|
||||
}
|
||||
/// Modify the world after the [`Criteria`] in the system batch allows the systems to run.
|
||||
///
|
||||
/// This can be great if this Criteria limits the execution of systems based off of resources.
|
||||
/// A `FixedTimestep` criteria would use this to replace the [`DeltaTime`] resource in the
|
||||
/// world to match the timestep time.
|
||||
fn modify_world(&mut self, world: NonNull<World>);
|
||||
|
||||
/// Undo modifications to the world after the systems in the batch have been executed.
|
||||
///
|
||||
/// The `FixedTimestep` criteria (see docs for [`Criteria::modify_world`]) uses this
|
||||
/// to replace the [`DeltaTime`] resource with its original value before it was replaced.
|
||||
fn undo_world_modifications(&mut self, world: NonNull<World>);
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
use std::{collections::{HashMap, VecDeque, HashSet}, ptr::NonNull};
|
||||
|
||||
use tracing::{debug_span, info_span, instrument};
|
||||
|
||||
use super::System;
|
||||
|
||||
use crate::{World, CommandQueue, Commands};
|
||||
|
@ -58,7 +60,9 @@ impl GraphExecutor {
|
|||
}
|
||||
|
||||
/// Executes the systems in the graph
|
||||
pub fn execute(&mut self, mut world_ptr: NonNull<World>, stop_on_error: bool) -> Result<Vec<GraphExecutorError>, GraphExecutorError> {
|
||||
#[instrument(skip(self, world_ptr, stop_on_error))]
|
||||
pub fn execute(&mut self, mut world_ptr: NonNull<World>, stop_on_error: bool)
|
||||
-> Result<Vec<GraphExecutorError>, GraphExecutorError> {
|
||||
let mut stack = VecDeque::new();
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
|
@ -71,6 +75,9 @@ impl GraphExecutor {
|
|||
while let Some(node) = stack.pop_front() {
|
||||
let system = self.systems.get_mut(node.as_str()).unwrap();
|
||||
|
||||
let span = info_span!("graph_exec", system=system.name.clone());
|
||||
let _e = span.enter();
|
||||
|
||||
if let Err(e) = system.system.execute(world_ptr)
|
||||
.map_err(|e| GraphExecutorError::SystemError(node, e)) {
|
||||
if stop_on_error {
|
||||
|
@ -81,6 +88,9 @@ impl GraphExecutor {
|
|||
unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error
|
||||
}
|
||||
|
||||
let deferred_span = debug_span!("deferred_exec");
|
||||
let _e = deferred_span.enter();
|
||||
|
||||
if let Err(e) = system.system.execute_deferred(world_ptr)
|
||||
.map_err(|e| GraphExecutorError::Command(e)) {
|
||||
|
||||
|
@ -99,23 +109,15 @@ impl GraphExecutor {
|
|||
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
|
||||
}
|
||||
commands.execute(world);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(possible_errors)
|
||||
}
|
||||
|
||||
fn topological_sort<'a>(&'a self, stack: &mut VecDeque<String>, visited: &mut HashSet<&'a str>, node: &'a GraphSystem) -> Result<(), GraphExecutorError> {
|
||||
fn topological_sort<'a>(&'a self, stack: &mut VecDeque<String>,
|
||||
visited: &mut HashSet<&'a str>, node: &'a GraphSystem) -> Result<(), GraphExecutorError> {
|
||||
|
||||
if !visited.contains(node.name.as_str()) {
|
||||
visited.insert(&node.name);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use std::{any::TypeId, cell::{Ref, RefMut}, collections::HashMap, ptr::NonNull};
|
||||
use std::{any::TypeId, collections::HashMap, ptr::NonNull};
|
||||
|
||||
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewState, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker};
|
||||
use atomic_refcell::{AtomicRef, AtomicRefMut};
|
||||
|
||||
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker};
|
||||
|
||||
/// The id of the entity for the Archetype.
|
||||
///
|
||||
|
@ -14,6 +16,7 @@ pub struct Record {
|
|||
pub index: ArchetypeEntityId,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct World {
|
||||
pub(crate) archetypes: HashMap<ArchetypeId, Archetype>,
|
||||
next_archetype_id: ArchetypeId,
|
||||
|
@ -126,51 +129,66 @@ impl World {
|
|||
}
|
||||
}
|
||||
|
||||
/// Insert a bundle into an existing entity. If the components are already existing on the
|
||||
/// entity, they will be updated, else the entity will be moved to a different Archetype
|
||||
/// that can store the entity. That may involve creating a new Archetype.
|
||||
/// Insert a component bundle into an existing entity.
|
||||
///
|
||||
/// If the components are already existing on the entity, they will be updated, else the
|
||||
/// entity will be moved to a different Archetype that can store the entity. That may
|
||||
/// involve creating a new Archetype.
|
||||
pub fn insert<B>(&mut self, entity: Entity, bundle: B)
|
||||
where
|
||||
B: Bundle
|
||||
{
|
||||
// TODO: If the archetype has a single entity, add a component column for the new
|
||||
// component instead of moving the entity to a brand new archetype.
|
||||
// TODO: If the entity already has the components in `bundle`, update the values of the
|
||||
// components with the bundle.
|
||||
|
||||
let tick = self.tick();
|
||||
|
||||
let record = self.entities.entity_record(entity).unwrap();
|
||||
let current_arch = self.archetypes.get(&record.id).unwrap();
|
||||
|
||||
let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id()).collect();
|
||||
let orig_col = col_types.clone();
|
||||
col_types.extend(bundle.type_ids());
|
||||
|
||||
let mut col_infos: Vec<ComponentInfo> = current_arch.columns.iter().map(|c| c.info).collect();
|
||||
col_infos.extend(bundle.info());
|
||||
let current_arch_len = current_arch.len();
|
||||
|
||||
let col_ptrs: Vec<(NonNull<u8>, ComponentInfo)> = current_arch.columns.iter().map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) }).collect();
|
||||
let mut contains_all = true;
|
||||
for id in bundle.type_ids() {
|
||||
contains_all = contains_all && current_arch.get_column(id).is_some();
|
||||
}
|
||||
|
||||
if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&col_types)) {
|
||||
let res_index = arch.reserve_one(entity);
|
||||
if contains_all {
|
||||
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
||||
let entry_idx = *current_arch.entity_indexes()
|
||||
.get(&entity).unwrap();
|
||||
|
||||
for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) {
|
||||
unsafe {
|
||||
let ptr = NonNull::new_unchecked(col_ptr.as_ptr()
|
||||
.add(res_index.0 as usize * col_info.layout().size()));
|
||||
let col = arch.get_column_mut(col_type).unwrap();
|
||||
col.set_at(res_index.0 as _, ptr, tick);
|
||||
}
|
||||
}
|
||||
|
||||
bundle.take(|data, type_id, _size| {
|
||||
let col = arch.get_column_mut(type_id).unwrap();
|
||||
unsafe { col.set_at(res_index.0 as _, data, tick); }
|
||||
col.len += 1;
|
||||
bundle.take(|ptr, id, _info| {
|
||||
let col = current_arch.get_column_mut(id).unwrap();
|
||||
unsafe { col.set_at(entry_idx.0 as _, ptr, tick) };
|
||||
});
|
||||
|
||||
arch.entity_ids.insert(entity, res_index);
|
||||
return;
|
||||
}
|
||||
|
||||
// contains the type ids for the old component columns + the ids for the new components
|
||||
let mut combined_column_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id()).collect();
|
||||
combined_column_types.extend(bundle.type_ids());
|
||||
|
||||
// contains the ComponentInfo for the old component columns + the info for the new components
|
||||
let mut combined_column_infos: Vec<ComponentInfo> = current_arch.columns.iter().map(|c| c.info).collect();
|
||||
combined_column_infos.extend(bundle.info());
|
||||
|
||||
// pointers only for the old columns
|
||||
let old_columns: Vec<(NonNull<u8>, ComponentInfo)> = current_arch.columns.iter()
|
||||
.map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) })
|
||||
.collect();
|
||||
|
||||
if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&combined_column_types)) {
|
||||
let mut dbun = DynamicBundle::new();
|
||||
// move old entity components into new archetype columns
|
||||
for (col_ptr, col_info) in old_columns.into_iter() {
|
||||
unsafe {
|
||||
let ptr = NonNull::new_unchecked(col_ptr.as_ptr()
|
||||
.add(record.index.0 as usize * col_info.layout().size()));
|
||||
dbun.push_unknown(ptr, col_info);
|
||||
}
|
||||
}
|
||||
dbun.push_bundle(bundle);
|
||||
|
||||
let res_index = arch.add_entity(entity, dbun, &tick);
|
||||
arch.ensure_synced();
|
||||
|
||||
let new_record = Record {
|
||||
id: arch.id(),
|
||||
|
@ -178,9 +196,28 @@ impl World {
|
|||
};
|
||||
self.entities.insert_entity_record(entity, new_record);
|
||||
} else {
|
||||
if current_arch_len == 1 {
|
||||
// if this entity is the only entity for this archetype, add more columns to it
|
||||
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
||||
current_arch.extend(&tick, vec![bundle]);
|
||||
return;
|
||||
}
|
||||
|
||||
let new_arch_id = self.next_archetype_id.increment();
|
||||
let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos);
|
||||
let entity_arch_id = archetype.add_entity(entity, bundle, &tick);
|
||||
let mut archetype = Archetype::from_bundle_info(new_arch_id, combined_column_infos);
|
||||
|
||||
let mut dbun = DynamicBundle::new();
|
||||
for (column_ptr, column_info) in old_columns.into_iter() {
|
||||
unsafe {
|
||||
// ptr of component for the entity
|
||||
let comp_ptr = NonNull::new_unchecked(column_ptr.as_ptr()
|
||||
.add(record.index.0 as usize * column_info.layout().size()));
|
||||
dbun.push_unknown(comp_ptr, column_info);
|
||||
}
|
||||
}
|
||||
dbun.push_bundle(bundle);
|
||||
|
||||
let entity_arch_id = archetype.add_entity(entity, dbun, &tick);
|
||||
|
||||
self.archetypes.insert(new_arch_id, archetype);
|
||||
|
||||
|
@ -194,7 +231,104 @@ impl World {
|
|||
}
|
||||
|
||||
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
||||
current_arch.remove_entity(entity, &tick);
|
||||
if let Some((en, enar)) = current_arch.remove_entity(entity, &tick) {
|
||||
let rec = Record {
|
||||
id: current_arch.id(),
|
||||
index: enar
|
||||
};
|
||||
self.entities.insert_entity_record(en, rec);
|
||||
}
|
||||
|
||||
current_arch.ensure_synced();
|
||||
}
|
||||
|
||||
/// A method used for debugging implementation details of the ECS.
|
||||
///
|
||||
/// Here's an example of the output:
|
||||
/// ```nobuild
|
||||
/// Entities
|
||||
/// 1 in archetype 0 at 0
|
||||
/// 0 in archetype 1 at 0
|
||||
/// 2 in archetype 0 at 1
|
||||
/// 3 in archetype 2 at 0
|
||||
/// Arch 1 -- 1 entities
|
||||
/// Col 175564825027445222460146453544114453753
|
||||
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0
|
||||
/// Col 162279302565774655543278578489329315472
|
||||
/// 0: 0 0 32 65 0 0 32 65 0 0 32 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
|
||||
/// Col 24291284537013640759061027938209843602
|
||||
/// 0: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
/// Arch 2 -- 1 entities
|
||||
/// Col 175564825027445222460146453544114453753
|
||||
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 237 127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0
|
||||
/// Col 162279302565774655543278578489329315472
|
||||
/// 0: 0 0 76 66 0 0 170 66 0 0 136 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
|
||||
/// Col 142862377085187052737282554588643015580
|
||||
/// 0: 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
/// Arch 0 -- 2 entities
|
||||
/// Col 175564825027445222460146453544114453753
|
||||
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 32 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
|
||||
/// 1: 0 0 0 0 0 0 0 0 0 0 0 0 237 127 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 78 86 0 0
|
||||
/// Col 162279302565774655543278578489329315472
|
||||
/// 0: 0 0 112 65 0 0 112 65 0 0 112 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
|
||||
/// 1: 0 0 27 67 0 0 184 65 0 0 192 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 128 63 0 0 128 63 0 0 128 63 0 0 128 63 0 0 0 0
|
||||
/// Col 142862377085187052737282554588643015580
|
||||
/// 0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
/// 1: 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
/// Col 24291284537013640759061027938209843602
|
||||
/// 0: 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
/// 1: 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
/// Arch 3 -- 0 entities
|
||||
/// Col 175564825027445222460146453544114453753
|
||||
/// Col 162279302565774655543278578489329315472
|
||||
/// ```
|
||||
///
|
||||
/// This output prints all Entity ids, the archetype they're in, and the index that they're
|
||||
/// in inside of the archetype. Additionally, the archetypes are printing, including their
|
||||
/// columns type ids, and the contents of the type ids. This output can be used to debug
|
||||
/// the contents of entities inside the archetypes.
|
||||
///
|
||||
/// Below is a template of the output:
|
||||
/// ```nobuild
|
||||
/// Entities
|
||||
/// %ENTITY_ID% in archetype %ARCHETYPE_ID% at %INDEX%
|
||||
/// Arch ID -- %ARCHETYPE_LEN% entities
|
||||
/// %FOR EACH COL%
|
||||
/// Col COLUMN_COMPONENT_TYPE_ID
|
||||
/// %FOR EACH ENTITY%
|
||||
/// %ENTITY_INDEX%: %COMPONENT_BYTES%
|
||||
/// ```
|
||||
/// If the template above doesn't help you in understanding the output, read the source code
|
||||
/// of the function. The source code is pretty simple.
|
||||
pub fn debug_print_world(&self) {
|
||||
println!("Entities");
|
||||
for (en, rec) in &self.entities.arch_index {
|
||||
println!(" {} in archetype {} at {}", en.0, rec.id.0, rec.index.0);
|
||||
}
|
||||
|
||||
for arch in self.archetypes.values() {
|
||||
println!("Arch {} -- {} entities", arch.id().0, arch.len());
|
||||
|
||||
for col in &arch.columns {
|
||||
// no clue if doing this is stable, but this is a debug function so :shrug:
|
||||
let tyid: u128 = unsafe { std::mem::transmute(col.info.type_id().as_rust()) };
|
||||
println!(" Col {}", tyid);
|
||||
|
||||
for en in 0..col.len {
|
||||
// get the ptr starting at the component
|
||||
let p = col.borrow_ptr();
|
||||
let p = unsafe { p.as_ptr().add(en * col.info.layout().size()) };
|
||||
|
||||
print!(" {}: ", en);
|
||||
// print each byte of the component
|
||||
for i in 0..col.info.layout().size() {
|
||||
let d = unsafe { *p.add(i) };
|
||||
print!("{} ", d);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> {
|
||||
|
@ -225,6 +359,13 @@ impl World {
|
|||
v.into_iter()
|
||||
}
|
||||
|
||||
/// View into the world for a set of entities that satisfy the queries.
|
||||
pub fn filtered_view_iter<Q: AsQuery, F: AsQuery>(&self) -> ViewIter<Q::Query, F::Query> {
|
||||
let archetypes = self.archetypes.values().collect();
|
||||
let v = ViewState::new(self, Q::Query::new(), F::Query::new(), archetypes);
|
||||
v.into_iter()
|
||||
}
|
||||
|
||||
pub fn dynamic_view(&self) -> DynamicView {
|
||||
DynamicView::new(self)
|
||||
}
|
||||
|
@ -233,54 +374,102 @@ impl World {
|
|||
ViewOne::new(self, entity.id, T::Query::new())
|
||||
}
|
||||
|
||||
//pub fn view_one(&self, entity: EntityId) ->
|
||||
|
||||
pub fn add_resource<T: 'static>(&mut self, data: T) {
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data));
|
||||
/// Add a resource to the world.
|
||||
///
|
||||
/// Ticks the world.
|
||||
pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
|
||||
let tick = self.tick();
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(data, tick));
|
||||
}
|
||||
|
||||
pub fn add_resource_default<T: Default + 'static>(&mut self) {
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default()));
|
||||
/// Add the default value of a resource.
|
||||
///
|
||||
/// Ticks the world.
|
||||
///
|
||||
/// > Note: This will replace existing values.
|
||||
pub fn add_resource_default<T: ResourceObject + Default>(&mut self) {
|
||||
let tick = self.tick();
|
||||
self.resources.insert(TypeId::of::<T>(), ResourceData::new(T::default(), tick));
|
||||
}
|
||||
|
||||
/// Add the default value of a resource if it does not already exist.
|
||||
///
|
||||
/// Returns a boolean indicating if the resource was added. Ticks the world if the resource
|
||||
/// was added.
|
||||
pub fn add_resource_default_if_absent<T: ResourceObject + Default>(&mut self) -> bool {
|
||||
let id = TypeId::of::<T>();
|
||||
if !self.resources.contains_key(&id) {
|
||||
let tick = self.tick();
|
||||
self.resources.insert(id, ResourceData::new(T::default(), tick));
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a resource from the world, or insert it into the world with the provided
|
||||
/// `fn` and return it.
|
||||
pub fn get_resource_or_else<T: 'static, F>(&mut self, f: F) -> RefMut<T>
|
||||
///
|
||||
/// Ticks the world.
|
||||
pub fn get_resource_or_else<T: ResourceObject, F>(&mut self, f: F) -> AtomicRefMut<T>
|
||||
where
|
||||
F: Fn() -> T + 'static
|
||||
{
|
||||
self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(f()))
|
||||
.get_mut()
|
||||
let tick = self.tick();
|
||||
let res = self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(f(), tick));
|
||||
res.tick.tick_to(&tick);
|
||||
res.get_mut()
|
||||
}
|
||||
|
||||
/// Get a resource from the world, or insert it into the world as its default.
|
||||
pub fn get_resource_or_default<T: Default + 'static>(&mut self) -> RefMut<T>
|
||||
///
|
||||
/// Ticks the world.
|
||||
pub fn get_resource_or_default<T: ResourceObject + Default>(&mut self) -> AtomicRefMut<T>
|
||||
{
|
||||
self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(T::default()))
|
||||
.get_mut()
|
||||
let tick = self.tick();
|
||||
let res = self.resources.entry(TypeId::of::<T>())
|
||||
.or_insert_with(|| ResourceData::new(T::default(), tick));
|
||||
res.tick.tick_to(&tick);
|
||||
res.get_mut()
|
||||
}
|
||||
|
||||
/// Gets a resource from the World.
|
||||
///
|
||||
/// Will panic if the resource is not in the world. See [`World::try_get_resource`] for
|
||||
/// a function that returns an option.
|
||||
pub fn get_resource<T: 'static>(&self) -> Ref<T> {
|
||||
pub fn get_resource<T: ResourceObject>(&self) -> AtomicRef<T> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
|
||||
.get()
|
||||
}
|
||||
|
||||
/// Returns a boolean indicating if the resource changed.
|
||||
///
|
||||
/// This will return false if the resource doesn't exist.
|
||||
pub fn has_resource_changed<T: ResourceObject>(&self) -> bool {
|
||||
let tick = self.current_tick();
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.map(|r| r.changed(tick))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns the [`Tick`] that the resource was last modified at.
|
||||
pub fn resource_tick<T: ResourceObject>(&self) -> Option<Tick> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.map(|r| r.tick.current())
|
||||
}
|
||||
|
||||
/// Returns boolean indicating if the World contains a resource of type `T`.
|
||||
pub fn has_resource<T: 'static>(&self) -> bool {
|
||||
pub fn has_resource<T: ResourceObject>(&self) -> bool {
|
||||
self.resources.contains_key(&TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Attempts to get a resource from the World.
|
||||
///
|
||||
/// Returns `None` if the resource was not found.
|
||||
pub fn try_get_resource<T: 'static>(&self) -> Option<Ref<T>> {
|
||||
pub fn try_get_resource<T: ResourceObject>(&self) -> Option<AtomicRef<T>> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.and_then(|r| r.try_get())
|
||||
}
|
||||
|
@ -289,18 +478,32 @@ impl World {
|
|||
///
|
||||
/// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for
|
||||
/// a function that returns an option.
|
||||
pub fn get_resource_mut<T: 'static>(&self) -> RefMut<T> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.expect(&format!("World is missing resource of type '{}'", std::any::type_name::<T>()))
|
||||
.get_mut()
|
||||
///
|
||||
/// Ticks the world.
|
||||
pub fn get_resource_mut<T: ResourceObject>(&self) -> AtomicRefMut<T> {
|
||||
self.try_get_resource_mut::<T>()
|
||||
.unwrap_or_else(|| panic!("World is missing resource of type '{}'", std::any::type_name::<T>()))
|
||||
}
|
||||
|
||||
/// Attempts to get a mutable borrow of a resource from the World.
|
||||
///
|
||||
/// Returns `None` if the resource was not found.
|
||||
pub fn try_get_resource_mut<T: 'static>(&self) -> Option<RefMut<T>> {
|
||||
/// Returns `None` if the resource was not found. Ticks the world if the resource was found.
|
||||
pub fn try_get_resource_mut<T: ResourceObject>(&self) -> Option<AtomicRefMut<T>> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.and_then(|r| r.try_get_mut())
|
||||
.and_then(|r| {
|
||||
// now that the resource was retrieved, tick the world and the resource
|
||||
let new_tick = self.tick();
|
||||
r.tick.tick_to(&new_tick);
|
||||
r.try_get_mut()
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the corresponding [`ResourceData`].
|
||||
///
|
||||
/// > Note: If you borrow the resource mutably, the world and the resource will not be ticked.
|
||||
pub fn try_get_resource_data<T: ResourceObject>(&self) -> Option<ResourceData> {
|
||||
self.resources.get(&TypeId::of::<T>())
|
||||
.map(|r| r.clone())
|
||||
}
|
||||
|
||||
/// Increments the TickTracker which is used for tracking changes to components.
|
||||
|
@ -326,11 +529,19 @@ impl World {
|
|||
self.resources.get(&TypeId::of::<T>())
|
||||
.map(|d| unsafe { NonNull::new_unchecked(d.data.as_ptr() as *mut T) })
|
||||
}
|
||||
|
||||
pub fn archetype_count(&self) -> usize {
|
||||
self.archetypes.len()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Ensure that all non-send resources are only accessible on the main thread.
|
||||
unsafe impl Send for World {}
|
||||
unsafe impl Sync for World {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{tests::{Vec2, Vec3}, query::TickOf};
|
||||
use crate::{query::TickOf, tests::{Vec2, Vec3}, Entity};
|
||||
|
||||
use super::World;
|
||||
|
||||
|
@ -404,16 +615,14 @@ mod tests {
|
|||
#[test]
|
||||
fn resource_multi_borrow() {
|
||||
let mut world = World::new();
|
||||
{
|
||||
let counter = SimpleCounter(4582);
|
||||
world.add_resource(counter);
|
||||
}
|
||||
let counter = SimpleCounter(4582);
|
||||
world.add_resource(counter);
|
||||
|
||||
// test multiple borrows at the same time
|
||||
let counter = world.get_resource::<SimpleCounter>();
|
||||
assert_eq!(counter.0, 4582);
|
||||
let counter2 = world.get_resource::<SimpleCounter>();
|
||||
assert_eq!(counter2.0, 4582);
|
||||
assert_eq!(counter.0, 4582);
|
||||
assert_eq!(counter2.0, 4582);
|
||||
}
|
||||
|
||||
|
@ -425,7 +634,7 @@ mod tests {
|
|||
world.add_resource(counter);
|
||||
}
|
||||
|
||||
// test multiple borrows at the same time
|
||||
// test that its only possible to get a single mutable borrow
|
||||
let counter = world.get_resource_mut::<SimpleCounter>();
|
||||
assert_eq!(counter.0, 4582);
|
||||
assert!(world.try_get_resource_mut::<SimpleCounter>().is_none());
|
||||
|
@ -453,6 +662,44 @@ mod tests {
|
|||
assert!(world.view_one::<&Vec3>(e).get().is_some())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_multiple_times() {
|
||||
let v2s = &[Vec2::rand(), Vec2::rand(), Vec2::rand()];
|
||||
let v3s = &[Vec3::rand(), Vec3::rand(), Vec3::rand()];
|
||||
|
||||
let mut world = World::new();
|
||||
let e1 = world.spawn(v2s[0]);
|
||||
let e2 = world.spawn(v2s[1]);
|
||||
let e3 = world.spawn(v2s[2]);
|
||||
println!("Spawned entities");
|
||||
|
||||
let ev2 = world.view_one::<&Vec2>(e2).get()
|
||||
.expect("Failed to find Vec2 and Vec3 on inserted entity!");
|
||||
assert_eq!(*ev2, v2s[1]);
|
||||
drop(ev2);
|
||||
|
||||
let insert_and_assert = |world: &mut World, e: Entity, v2: Vec2, v3: Vec3| {
|
||||
println!("inserting entity");
|
||||
world.insert(e, (v3,));
|
||||
println!("inserted entity");
|
||||
|
||||
let (ev2, ev3) = world.view_one::<(&Vec2, &Vec3)>(e).get()
|
||||
.expect("Failed to find Vec2 and Vec3 on inserted entity!");
|
||||
assert_eq!(*ev2, v2);
|
||||
assert_eq!(*ev3, v3);
|
||||
};
|
||||
|
||||
insert_and_assert(&mut world, e2, v2s[1], v3s[1]);
|
||||
println!("Entity 2 is good");
|
||||
insert_and_assert(&mut world, e3, v2s[2], v3s[2]);
|
||||
println!("Entity 3 is good");
|
||||
assert_eq!(world.archetypes.len(), 2);
|
||||
println!("No extra archetypes were created");
|
||||
|
||||
insert_and_assert(&mut world, e1, v2s[0], v3s[0]);
|
||||
println!("Entity 1 is good");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn view_one() {
|
||||
let v = Vec2::rand();
|
||||
|
@ -468,11 +715,6 @@ mod tests {
|
|||
fn view_change_tracking() {
|
||||
let mut world = World::new();
|
||||
|
||||
/* let v = Vec2::rand();
|
||||
world.spawn((v,));
|
||||
let v = Vec2::rand();
|
||||
world.spawn((v,)); */
|
||||
|
||||
println!("spawning");
|
||||
world.spawn((Vec2::new(10.0, 10.0),));
|
||||
world.spawn((Vec2::new(5.0, 5.0),));
|
||||
|
@ -491,4 +733,39 @@ mod tests {
|
|||
assert!(tick >= world_tick);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests replacing components using World::insert
|
||||
#[test]
|
||||
fn entity_insert_replace() {
|
||||
let mut world = World::new();
|
||||
let first = world.spawn((Vec2::new(10.0, 10.0),));
|
||||
let second = world.spawn((Vec2::new(5.0, 5.0),));
|
||||
|
||||
world.insert(first, Vec2::new(50.0, 50.0));
|
||||
|
||||
let pos = world.view_one::<&mut Vec2>(first).get().unwrap();
|
||||
assert_eq!(*pos, Vec2::new(50.0, 50.0));
|
||||
drop(pos);
|
||||
|
||||
let pos = world.view_one::<&mut Vec2>(second).get().unwrap();
|
||||
assert_eq!(*pos, Vec2::new(5.0, 5.0));
|
||||
}
|
||||
|
||||
/// Tests resource change checks
|
||||
#[test]
|
||||
fn resource_changed() {
|
||||
let mut world = World::new();
|
||||
world.add_resource(SimpleCounter(50));
|
||||
|
||||
assert!(world.has_resource_changed::<SimpleCounter>());
|
||||
|
||||
world.spawn(Vec2::new(50.0, 50.0));
|
||||
|
||||
assert!(!world.has_resource_changed::<SimpleCounter>());
|
||||
|
||||
let mut counter = world.get_resource_mut::<SimpleCounter>();
|
||||
counter.0 += 100;
|
||||
|
||||
assert!(world.has_resource_changed::<SimpleCounter>());
|
||||
}
|
||||
}
|
|
@ -4,20 +4,26 @@ version = "0.0.1"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lyra-game-derive = { path = "./lyra-game-derive" }
|
||||
lyra-resource = { path = "../lyra-resource" }
|
||||
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
||||
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
|
||||
lyra-math = { path = "../lyra-math" }
|
||||
lyra-scene = { path = "../lyra-scene" }
|
||||
wgsl_preprocessor = { path = "../wgsl-preprocessor" }
|
||||
|
||||
winit = "0.28.1"
|
||||
wgpu = { version = "0.15.1", features = [ "expose-ids"] }
|
||||
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] }
|
||||
tracing-log = "0.1.3"
|
||||
tracing-appender = "0.2.2"
|
||||
wgpu = "0.15.1"
|
||||
tracing-tracy = { version = "0.11.0", optional = true }
|
||||
|
||||
async-std = { version = "1.12.0", features = [ "unstable", "attributes" ] }
|
||||
cfg-if = "1"
|
||||
bytemuck = { version = "1.12", features = [ "derive" ] }
|
||||
bytemuck = { version = "1.12", features = [ "derive", "min_const_generics" ] }
|
||||
image = { version = "0.24", default-features = false, features = ["png", "jpeg"] }
|
||||
anyhow = "1.0"
|
||||
instant = "0.1"
|
||||
|
@ -29,3 +35,12 @@ quote = "1.0.29"
|
|||
uuid = { version = "1.5.0", features = ["v4", "fast-rng"] }
|
||||
itertools = "0.11.0"
|
||||
thiserror = "1.0.56"
|
||||
unique = "0.9.1"
|
||||
rustc-hash = "1.1.0"
|
||||
petgraph = { version = "0.6.5", features = ["matrix_graph"] }
|
||||
bind_match = "0.1.2"
|
||||
round_mult = "0.1.3"
|
||||
fast_poisson = { version = "1.0.0", features = ["single_precision"] }
|
||||
|
||||
[features]
|
||||
tracy = ["dep:tracing-tracy"]
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "lyra-game-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"
|
|
@ -0,0 +1,35 @@
|
|||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
#[proc_macro_derive(RenderGraphLabel)]
|
||||
pub fn derive_render_graph_label(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
|
||||
let type_ident = &input.ident;
|
||||
|
||||
proc_macro::TokenStream::from(quote! {
|
||||
impl #impl_generics crate::render::graph::RenderGraphLabel for #type_ident #ty_generics #where_clause {
|
||||
fn rc_clone(&self) -> std::rc::Rc<dyn crate::render::graph::RenderGraphLabel> {
|
||||
std::rc::Rc::new(self.clone())
|
||||
}
|
||||
|
||||
/* fn as_dyn(&self) -> &dyn crate::render::graph::RenderGraphLabel {
|
||||
&self
|
||||
}
|
||||
|
||||
fn as_partial_eq(&self) -> &dyn PartialEq<dyn crate::render::graph::RenderGraphLabel> {
|
||||
self
|
||||
} */
|
||||
|
||||
fn as_label_hash(&self) -> u64 {
|
||||
let tyid = ::std::any::TypeId::of::<Self>();
|
||||
|
||||
let mut s = ::std::hash::DefaultHasher::new();
|
||||
::std::hash::Hash::hash(&tyid, &mut s);
|
||||
::std::hash::Hash::hash(self, &mut s);
|
||||
::std::hash::Hasher::finish(&s)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
use std::any::Any;
|
||||
|
||||
pub trait CastableAny: Send + Sync + 'static {
|
||||
/// A trait that is implemented for types that are `Send + Sync + 'static`.
|
||||
pub trait AsSyncAny: Send + Sync + 'static {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
}
|
||||
|
||||
/// Implements this trait for anything that fits the type bounds
|
||||
impl<T: Send + Sync + 'static> CastableAny for T {
|
||||
impl<T: Send + Sync + 'static> AsSyncAny for T {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
|
@ -4,9 +4,15 @@ use lyra_reflect::Reflect;
|
|||
|
||||
use crate::{plugin::Plugin, game::GameStages};
|
||||
|
||||
#[derive(Clone, Component, Default, Reflect)]
|
||||
#[derive(Clone, Copy, Component, Default, Reflect)]
|
||||
pub struct DeltaTime(f32, #[reflect(skip)] Option<Instant>);
|
||||
|
||||
impl From<f32> for DeltaTime {
|
||||
fn from(value: f32) -> Self {
|
||||
DeltaTime(value, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for DeltaTime {
|
||||
type Target = f32;
|
||||
|
||||
|
|
|
@ -1,15 +1,82 @@
|
|||
use std::{collections::{HashMap, VecDeque}, any::{TypeId, Any}, cell::RefCell};
|
||||
use std::{any::{Any, TypeId}, collections::{HashMap, VecDeque}, marker::PhantomData, mem::{self, MaybeUninit}, ops::Range};
|
||||
|
||||
use crate::{castable_any::CastableAny, plugin::Plugin};
|
||||
use crate::plugin::Plugin;
|
||||
|
||||
pub trait Event: Clone + Send + Sync + 'static {}
|
||||
impl<T: Clone + Send + Sync + 'static> Event for T {}
|
||||
|
||||
pub type Events<T> = VecDeque<T>;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TypeErasedBuffer {
|
||||
type_id: TypeId,
|
||||
item_size: usize,
|
||||
data: Vec<MaybeUninit<u8>>,
|
||||
fn_drop_item: fn(item: *mut ()),
|
||||
}
|
||||
|
||||
impl Drop for TypeErasedBuffer {
|
||||
fn drop(&mut self) {
|
||||
let range = self.data.as_mut_ptr_range();
|
||||
let mut current = range.start;
|
||||
let end = range.end;
|
||||
|
||||
while current < end {
|
||||
// SAFETY: current pointer will either be the start of the buffer, or at the start of
|
||||
// the next item
|
||||
(self.fn_drop_item)(current.cast());
|
||||
|
||||
// SAFETY: the items are placed right after eachother
|
||||
current = unsafe { current.add(self.item_size) };
|
||||
}
|
||||
|
||||
// Safety: all of the events were just dropped
|
||||
unsafe { self.data.set_len(0) };
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeErasedBuffer {
|
||||
pub fn new<T: 'static>() -> Self {
|
||||
Self {
|
||||
type_id: TypeId::of::<T>(),
|
||||
item_size: mem::size_of::<T>(),
|
||||
data: Default::default(),
|
||||
// dropped immediately after the read
|
||||
fn_drop_item: |item| unsafe { item.cast::<T>().drop_in_place() },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push<T: 'static>(&mut self, value: T) {
|
||||
debug_assert!(TypeId::of::<T>() == self.type_id, "The type of the buffer does not match the type that was added to it");
|
||||
|
||||
// reserve enough bytes to store T
|
||||
let old_len = self.data.len();
|
||||
self.data.reserve(mem::size_of::<T>());
|
||||
|
||||
// Get a pointer to the end of the data.
|
||||
// SAFETY: we just reserved enough memory to store the data.
|
||||
let end_ptr = unsafe { self.data.as_mut_ptr().add(old_len) };
|
||||
|
||||
unsafe {
|
||||
// write the command and its runner into the buffer
|
||||
end_ptr.cast::<T>()
|
||||
// written unaligned to keep everything packed
|
||||
.write_unaligned(value);
|
||||
|
||||
// we wrote to the vec's buffer without using its api, so we need manually
|
||||
// set the length of the vec.
|
||||
self.data.set_len(old_len + mem::size_of::<T>());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len() / self.item_size
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventQueue {
|
||||
events: HashMap<TypeId, RefCell<Box<dyn CastableAny>>>,
|
||||
event_write_queue: HashMap<TypeId, RefCell<Box<dyn CastableAny>>>
|
||||
events: HashMap<TypeId, TypeErasedBuffer>,
|
||||
event_write_queue: HashMap<TypeId, TypeErasedBuffer>,
|
||||
}
|
||||
|
||||
impl Default for EventQueue {
|
||||
|
@ -32,7 +99,7 @@ impl EventQueue {
|
|||
E: Event
|
||||
{
|
||||
// the compiler wants me to explicit right here for some reason
|
||||
let default = || RefCell::new(Box::<Events::<E>>::default() as Box<dyn CastableAny>);
|
||||
/* let default = || RefCell::new(Box::<Events::<E>>::default() as Box<dyn AsSyncAny>);
|
||||
|
||||
// Get, or create, a list of events of this type
|
||||
let type_id = event.type_id();
|
||||
|
@ -42,7 +109,13 @@ impl EventQueue {
|
|||
|
||||
// downcast resource as an events storage
|
||||
let e: &mut Events<E> = events.as_mut().as_any_mut().downcast_mut().unwrap();
|
||||
e.push_back(event);
|
||||
e.push_back(event); */
|
||||
|
||||
let type_id = event.type_id();
|
||||
let events = self.event_write_queue.entry(type_id)
|
||||
.or_insert_with(|| TypeErasedBuffer::new::<E>());
|
||||
|
||||
events.push(event);
|
||||
}
|
||||
|
||||
// Clear events, this should happen at the start of every tick since events are cloned
|
||||
|
@ -60,13 +133,59 @@ impl EventQueue {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn read_events<E>(&self) -> Option<Events<E>>
|
||||
pub fn read_events<E>(&self) -> Option<EventReader<E>>
|
||||
where
|
||||
E: Event
|
||||
{
|
||||
if let Some(event ) = self.events.get(&TypeId::of::<E>()) {
|
||||
let eref = event.borrow();
|
||||
Some(eref.as_ref().as_any().downcast_ref::<Events<E>>().unwrap().clone())
|
||||
self.events.get(&TypeId::of::<E>())
|
||||
.map(|event| EventReader::new(event.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EventReader<E: Event> {
|
||||
buf: TypeErasedBuffer,
|
||||
range: Option<Range<*mut MaybeUninit<u8>>>,
|
||||
_marker: PhantomData<E>,
|
||||
}
|
||||
|
||||
impl<E: Event> EventReader<E> {
|
||||
fn new(buffer: TypeErasedBuffer) -> Self {
|
||||
Self {
|
||||
buf: buffer,
|
||||
range: None,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.buf.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Event> Iterator for EventReader<E> {
|
||||
type Item = E;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.range.is_none() {
|
||||
self.range = Some(self.buf.data.as_mut_ptr_range());
|
||||
}
|
||||
|
||||
let range = self.range.as_mut().unwrap();
|
||||
|
||||
if range.start < range.end {
|
||||
// Retrieve the event in the buffer.
|
||||
// SAFETY: current pointer will either be the start of the buffer, or at the start
|
||||
// of the next event.
|
||||
let event = unsafe { range.start.cast::<E>().read_unaligned() };
|
||||
|
||||
// Advance the pointer to be after this event.
|
||||
range.start = unsafe { range.start.add(mem::size_of::<E>()) };
|
||||
|
||||
Some(event)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{sync::Arc, collections::VecDeque, ptr::NonNull};
|
|||
use async_std::task::block_on;
|
||||
|
||||
use lyra_ecs::{World, system::{System, IntoSystem}};
|
||||
use tracing::{info, error, Level};
|
||||
use tracing::{error, info, Level};
|
||||
use tracing_appender::non_blocking;
|
||||
use tracing_subscriber::{
|
||||
layer::SubscriberExt,
|
||||
|
@ -57,10 +57,12 @@ struct GameLoop {
|
|||
}
|
||||
|
||||
impl GameLoop {
|
||||
pub async fn new(window: Arc<Window>, world: World, staged_exec: StagedExecutor) -> GameLoop {
|
||||
pub async fn new(window: Arc<Window>, mut world: World, staged_exec: StagedExecutor) -> Self {
|
||||
let renderer = BasicRenderer::create_with_window(&mut world, window.clone()).await;
|
||||
|
||||
Self {
|
||||
window: Arc::clone(&window),
|
||||
renderer: Box::new(BasicRenderer::create_with_window(window).await),
|
||||
renderer: Box::new(renderer),
|
||||
|
||||
world,
|
||||
staged_exec,
|
||||
|
@ -68,7 +70,7 @@ impl GameLoop {
|
|||
}
|
||||
|
||||
pub async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
||||
self.renderer.on_resize(new_size);
|
||||
self.renderer.on_resize(&mut self.world, new_size);
|
||||
}
|
||||
|
||||
pub async fn on_init(&mut self) {
|
||||
|
@ -344,14 +346,23 @@ impl Game {
|
|||
pub async fn run(&mut self) {
|
||||
// init logging
|
||||
let (stdout_layer, _stdout_nb) = non_blocking(std::io::stdout());
|
||||
tracing_subscriber::registry()
|
||||
.with(fmt::layer().with_writer(stdout_layer))
|
||||
.with(filter::Targets::new()
|
||||
// done by prefix, so it includes all lyra subpackages
|
||||
.with_target("lyra", Level::DEBUG)
|
||||
.with_target("wgpu", Level::WARN)
|
||||
.with_default(Level::INFO))
|
||||
.init();
|
||||
{
|
||||
let t = tracing_subscriber::registry()
|
||||
.with(fmt::layer().with_writer(stdout_layer));
|
||||
|
||||
#[cfg(feature = "tracy")]
|
||||
let t = t.with(tracing_tracy::TracyLayer::default());
|
||||
|
||||
t.with(filter::Targets::new()
|
||||
// done by prefix, so it includes all lyra subpackages
|
||||
.with_target("lyra", Level::DEBUG)
|
||||
.with_target("wgsl_preprocessor", Level::DEBUG)
|
||||
.with_target("wgpu", Level::WARN)
|
||||
.with_target("winit", Level::DEBUG)
|
||||
.with_default(Level::INFO))
|
||||
.init();
|
||||
}
|
||||
|
||||
|
||||
let world = self.world.take().unwrap_or_default();
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use lyra_reflect::Reflect;
|
|||
|
||||
use crate::{plugin::Plugin, game::GameStages, EventQueue};
|
||||
|
||||
use super::{Button, KeyCode, InputButtons, MouseMotion};
|
||||
use super::{Button, InputButtons, KeyCode, MouseButton, MouseMotion};
|
||||
|
||||
pub trait ActionLabel: Debug {
|
||||
/// Returns a unique hash of the label.
|
||||
|
@ -105,15 +105,6 @@ pub enum GamepadInput {
|
|||
Axis(GamepadAxis),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
Other(u16),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum MouseAxis {
|
||||
|
@ -530,10 +521,10 @@ fn actions_system(world: &mut World) -> anyhow::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
let motion_avg = if let Some(mut mouse_events) = mouse_events {
|
||||
let motion_avg = if let Some(mouse_events) = mouse_events {
|
||||
let count = mouse_events.len();
|
||||
let mut sum = Vec2::ZERO;
|
||||
while let Some(mm) = mouse_events.pop_front() {
|
||||
for mm in mouse_events {
|
||||
sum += mm.delta;
|
||||
}
|
||||
Some(sum / count as f32)
|
||||
|
|
|
@ -92,12 +92,19 @@ impl<T: Button> InputButtons<T> {
|
|||
pub fn was_just_pressed(&self, button: T) -> bool {
|
||||
let hash = Self::get_button_hash(&button);
|
||||
match self.button_events.get(&hash) {
|
||||
Some(button_event) => match button_event {
|
||||
// this if statement should always be true, but just in case ;)
|
||||
ButtonEvent::JustPressed(b) if button == *b => true,
|
||||
_ => false,
|
||||
},
|
||||
Some(button_event) => matches!(button_event, ButtonEvent::JustPressed(b) if button == *b),
|
||||
None => false
|
||||
}
|
||||
}
|
||||
|
||||
/// Update any JustPressed events into Pressed events
|
||||
///
|
||||
/// This must be done so that a key does not stay as JustPressed between multiple ticks
|
||||
pub fn update(&mut self) {
|
||||
for bev in self.button_events.values_mut() {
|
||||
if let ButtonEvent::JustPressed(btn) = bev {
|
||||
*bev = ButtonEvent::Pressed(btn.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -83,7 +83,7 @@ impl Touches {
|
|||
/// A mouse button event
|
||||
///
|
||||
/// Translated `WindowEvent::MouseButton` from `winit` crate
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
|
|
|
@ -102,13 +102,16 @@ impl crate::ecs::system::System for InputSystem {
|
|||
let queue = world.try_get_resource_mut::<EventQueue>()
|
||||
.and_then(|q| q.read_events::<InputEvent>());
|
||||
|
||||
let mut e = world.get_resource_or_else(InputButtons::<winit::event::VirtualKeyCode>::new);
|
||||
e.update();
|
||||
drop(e);
|
||||
|
||||
if queue.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut events = queue.unwrap();
|
||||
|
||||
while let Some(event) = events.pop_front() {
|
||||
let events = queue.unwrap();
|
||||
for event in events {
|
||||
self.process_event(world, &event);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#![feature(hash_extract_if)]
|
||||
#![feature(lint_reasons)]
|
||||
#![feature(trait_alias)]
|
||||
#![feature(map_many_mut)]
|
||||
|
||||
extern crate self as lyra_engine;
|
||||
|
||||
|
@ -8,7 +9,7 @@ pub mod game;
|
|||
pub mod render;
|
||||
pub mod resources;
|
||||
pub mod input;
|
||||
pub mod castable_any;
|
||||
pub mod as_any;
|
||||
pub mod plugin;
|
||||
pub mod change_tracker;
|
||||
|
||||
|
|
|
@ -0,0 +1,297 @@
|
|||
use std::{alloc::Layout, cmp, marker::PhantomData, mem};
|
||||
|
||||
use std::{alloc, ptr};
|
||||
use unique::Unique;
|
||||
|
||||
/// A [`Vec`] with its elements aligned to a runtime alignment value.
|
||||
pub struct AVec<T> {
|
||||
buf: Unique<u8>,
|
||||
cap: usize,
|
||||
len: usize,
|
||||
align: usize,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> AVec<T> {
|
||||
// Tiny Vecs are dumb. Skip to:
|
||||
// - 8 if the element size is 1, because any heap allocators are likely
|
||||
// to round up a request of less than 8 bytes to at least 8 bytes.
|
||||
// - 4 if elements are moderate-sized (<= 1 KiB).
|
||||
// - 1 otherwise, to avoid wasting too much space for very short Vecs.
|
||||
//
|
||||
// Taken from Rust's standard library RawVec
|
||||
pub(crate) const MIN_NON_ZERO_CAP: usize = if mem::size_of::<T>() == 1 {
|
||||
8
|
||||
} else if mem::size_of::<T>() <= 1024 {
|
||||
4
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
#[inline]
|
||||
pub fn new(alignment: usize) -> Self {
|
||||
debug_assert!(mem::size_of::<T>() > 0, "ZSTs not yet supported");
|
||||
|
||||
Self {
|
||||
buf: Unique::dangling(),
|
||||
cap: 0,
|
||||
len: 0,
|
||||
align: alignment,
|
||||
_marker: PhantomData
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs a new, empty `AVec` with at least the specified capacity.
|
||||
///
|
||||
/// The aligned vector will be able to hold at least `capacity` elements without reallocating.
|
||||
/// This method may allocate for more elements than `capacity`. If `capacity` is zero,
|
||||
/// the vector will not allocate.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the capacity exceeds `usize::MAX` bytes.
|
||||
#[inline]
|
||||
pub fn with_capacity(alignment: usize, capacity: usize) -> Self {
|
||||
let mut s = Self::new(alignment);
|
||||
|
||||
if capacity > 0 {
|
||||
unsafe {
|
||||
s.grow_amortized(0, capacity);
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
/// Calculates the size of the 'slot' for a single **aligned** item.
|
||||
#[inline(always)]
|
||||
fn slot_size(&self) -> usize {
|
||||
let a = self.align - 1;
|
||||
(mem::align_of::<T>() + (a)) & !a
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the new capacity exceeds `usize::MAX` bytes.
|
||||
#[inline]
|
||||
unsafe fn grow_amortized(&mut self, len: usize, additional: usize) {
|
||||
debug_assert!(additional > 0);
|
||||
|
||||
let required_cap = len.checked_add(additional)
|
||||
.expect("Capacity overflow");
|
||||
|
||||
let cap = cmp::max(self.cap * 2, required_cap);
|
||||
let cap = cmp::max(Self::MIN_NON_ZERO_CAP, cap);
|
||||
|
||||
let new_layout = Layout::from_size_align_unchecked(cap * self.slot_size(), self.align);
|
||||
|
||||
let ptr = alloc::alloc(new_layout);
|
||||
self.buf = Unique::new_unchecked(ptr);
|
||||
self.cap = cap;
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the new capacity exceeds `usize::MAX` bytes.
|
||||
#[inline]
|
||||
unsafe fn grow_exact(&mut self, len: usize, additional: usize) {
|
||||
debug_assert!(additional > 0);
|
||||
|
||||
let cap = len.checked_add(additional)
|
||||
.expect("Capacity overflow");
|
||||
|
||||
let new_layout = Layout::from_size_align_unchecked(cap * self.slot_size(), self.align);
|
||||
|
||||
let ptr = alloc::alloc(new_layout);
|
||||
self.buf = Unique::new_unchecked(ptr);
|
||||
self.cap = cap;
|
||||
}
|
||||
|
||||
/// Reserves capacity for at least `additional` more elements.
|
||||
///
|
||||
/// The collection may reserve more space to speculatively avoid frequent reallocations.
|
||||
/// After calling `reserve`, capacity will be greater than or equal to
|
||||
/// `self.len() + additional`. Does nothing if capacity is already sufficient.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the new capacity exceeds `usize::MAX` bytes.
|
||||
#[inline]
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
debug_assert!(additional > 0);
|
||||
|
||||
let remaining = self.capacity().wrapping_sub(self.len);
|
||||
|
||||
if additional > remaining {
|
||||
unsafe { self.grow_amortized(self.len, additional) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Reserves capacity for `additional` more elements.
|
||||
///
|
||||
/// Unlike [`reserve`], this will not over-allocate to speculatively avoid frequent
|
||||
/// reallocations. After calling `reserve_exact`, capacity will be equal to
|
||||
/// `self.len() + additional`. Does nothing if the capacity is already sufficient.
|
||||
///
|
||||
/// Prefer [`reserve`] if future insertions are expected.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the new capacity exceeds `usize::MAX` bytes.
|
||||
#[inline]
|
||||
pub fn reserve_exact(&mut self, additional: usize) {
|
||||
let remaining = self.capacity().wrapping_sub(self.len);
|
||||
|
||||
if additional > remaining {
|
||||
unsafe { self.grow_exact(self.len, additional) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends an element to the back of the collection.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the new capacity exceeds `usize::MAX` bytes.
|
||||
#[inline]
|
||||
pub fn push(&mut self, val: T) {
|
||||
if self.len == self.cap {
|
||||
self.reserve(self.slot_size());
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// SAFETY: the length is ensured to be less than the capacity.
|
||||
self.set_at_unchecked(self.len, val);
|
||||
}
|
||||
|
||||
self.len += 1;
|
||||
}
|
||||
|
||||
/// Sets an element at position `idx` within the vector to `val`.
|
||||
///
|
||||
/// # Unsafe
|
||||
///
|
||||
/// If `self.len > idx`, bytes past the length of the vector will be written to, potentially
|
||||
/// also writing past the capacity of the vector.
|
||||
#[inline(always)]
|
||||
unsafe fn set_at_unchecked(&mut self, idx: usize, val: T) {
|
||||
let ptr = self.buf
|
||||
.as_ptr()
|
||||
.add(idx * self.slot_size());
|
||||
|
||||
std::ptr::write(ptr.cast::<T>(), val);
|
||||
}
|
||||
|
||||
/// Sets an element at position `idx` within the vector to `val`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `idx >= self.len`.
|
||||
#[inline(always)]
|
||||
pub fn set_at(&mut self, idx: usize, val: T) {
|
||||
assert!(self.len > idx);
|
||||
|
||||
unsafe {
|
||||
self.set_at_unchecked(idx, val);
|
||||
}
|
||||
}
|
||||
|
||||
/// Shortens the vector, keeping the first `len` elements and dropping the rest.
|
||||
///
|
||||
/// If `len` is greater or equal to the vector’s current length, this has no effect.
|
||||
#[inline]
|
||||
pub fn truncate(&mut self, len: usize) {
|
||||
if len > self.len {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
// drop each element past the new length
|
||||
for i in len..self.len {
|
||||
let ptr = self.buf.as_ptr()
|
||||
.add(i * self.slot_size())
|
||||
.cast::<T>();
|
||||
|
||||
ptr::drop_in_place(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
self.len = len;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_ptr(&self) -> *const u8 {
|
||||
self.buf.as_ptr()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn as_mut_ptr(&self) -> *mut u8 {
|
||||
self.buf.as_ptr()
|
||||
}
|
||||
|
||||
/// Returns the alignment of the elements in the vector.
|
||||
#[inline(always)]
|
||||
pub fn align(&self) -> usize {
|
||||
self.align
|
||||
}
|
||||
|
||||
/// Returns the length of the vector.
|
||||
#[inline(always)]
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
/// Returns the capacity of the vector.
|
||||
///
|
||||
/// The capacity is the amount of elements that the vector can store without reallocating.
|
||||
#[inline(always)]
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.cap
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> AVec<T> {
|
||||
/// Resized the `AVec` in-place so that `len` is equal to `new_len`.
|
||||
///
|
||||
/// If `new_len` is greater than `len`, the `AVec` is extended by the difference, and
|
||||
/// each additional slot is filled with `value`. If `new_len` is less than `len`,
|
||||
/// the `AVec` will be truncated by to be `new_len`
|
||||
///
|
||||
/// This method requires `T` to implement [`Clone`] in order to clone the passed value.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the new capacity exceeds `usize::MAX` bytes.
|
||||
#[inline]
|
||||
pub fn resize(&mut self, new_len: usize, value: T) {
|
||||
if new_len > self.len {
|
||||
self.reserve(new_len - self.len);
|
||||
|
||||
unsafe {
|
||||
let mut ptr = self.buf
|
||||
.as_ptr().add(self.len * self.slot_size());
|
||||
|
||||
// write all elements besides the last one
|
||||
for _ in 1..new_len {
|
||||
std::ptr::write(ptr.cast::<T>(), value.clone());
|
||||
ptr = ptr.add(self.slot_size());
|
||||
self.len += 1;
|
||||
}
|
||||
|
||||
if new_len > 0 {
|
||||
// the last element can be written without cloning
|
||||
std::ptr::write(ptr.cast::<T>(), value.clone());
|
||||
self.len += 1;
|
||||
}
|
||||
|
||||
self.len = new_len;
|
||||
}
|
||||
} else {
|
||||
self.truncate(new_len);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,16 +38,43 @@ impl Projection {
|
|||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct CameraUniform {
|
||||
pub view_proj: glam::Mat4,
|
||||
// vec4 is used because of the uniforms 16 byte spacing requirement
|
||||
pub view_pos: glam::Vec4,
|
||||
/// The view matrix of the camera
|
||||
pub view: glam::Mat4,
|
||||
/// The inverse of the projection matrix of the camera
|
||||
pub inverse_projection: glam::Mat4,
|
||||
/// The view projection matrix
|
||||
pub view_projection: glam::Mat4,
|
||||
pub projection: glam::Mat4,
|
||||
/// The position of the camera
|
||||
pub position: glam::Vec3,
|
||||
pub tile_debug: u32,
|
||||
//_padding: [u8; 3],
|
||||
|
||||
}
|
||||
|
||||
impl Default for CameraUniform {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
view_proj: glam::Mat4::IDENTITY,
|
||||
view_pos: Default::default()
|
||||
view: glam::Mat4::IDENTITY,
|
||||
inverse_projection: glam::Mat4::IDENTITY,
|
||||
view_projection: glam::Mat4::IDENTITY,
|
||||
projection: glam::Mat4::IDENTITY,
|
||||
position: Default::default(),
|
||||
tile_debug: 0,
|
||||
//_padding: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CameraUniform {
|
||||
pub fn new(view: glam::Mat4, inverse_projection: glam::Mat4, view_projection: glam::Mat4, projection: glam::Mat4, position: glam::Vec3) -> Self {
|
||||
Self {
|
||||
view,
|
||||
inverse_projection,
|
||||
view_projection,
|
||||
projection,
|
||||
position,
|
||||
tile_debug: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,26 +106,38 @@ impl RenderCamera {
|
|||
self.aspect = size.width as f32 / size.height as f32;
|
||||
}
|
||||
|
||||
pub fn update_view_projection(&mut self, camera: &CameraComponent) -> &glam::Mat4 {
|
||||
/// Calculates the view projection, and the view
|
||||
///
|
||||
/// Returns: A tuple with the view projection as the first element, and the
|
||||
/// view matrix as the second.
|
||||
pub fn calc_view_projection(&mut self, camera: &CameraComponent) -> CameraUniform {
|
||||
let position = camera.transform.translation;
|
||||
let forward = camera.transform.forward();
|
||||
let up = camera.transform.up();
|
||||
|
||||
let view = glam::Mat4::look_to_rh(
|
||||
position,
|
||||
forward,
|
||||
up
|
||||
);
|
||||
|
||||
match camera.mode {
|
||||
CameraProjectionMode::Perspective => {
|
||||
let position = camera.transform.translation;
|
||||
let forward = camera.transform.forward();
|
||||
let up = camera.transform.up();
|
||||
|
||||
let view = glam::Mat4::look_to_rh(
|
||||
position,
|
||||
forward,
|
||||
up
|
||||
);
|
||||
|
||||
let proj = glam::Mat4::perspective_rh_gl(camera.fov.to_radians(), self.aspect, self.znear, self.zfar);
|
||||
|
||||
self.view_proj = OPENGL_TO_WGPU_MATRIX * proj * view;
|
||||
&self.view_proj
|
||||
//(&self.view_proj, view)
|
||||
|
||||
CameraUniform {
|
||||
view,
|
||||
inverse_projection: proj.inverse(),
|
||||
view_projection: self.view_proj,
|
||||
projection: proj,
|
||||
position,
|
||||
tile_debug: camera.debug as u32,
|
||||
}
|
||||
},
|
||||
CameraProjectionMode::Orthographic => {
|
||||
let position = camera.transform.translation;
|
||||
let target = camera.transform.rotation * glam::Vec3::new(0.0, 0.0, -1.0);
|
||||
let target = target.normalize();
|
||||
|
||||
|
@ -111,7 +150,15 @@ impl RenderCamera {
|
|||
let proj = glam::Mat4::orthographic_rh_gl(-size_x, size_x, -size_y, size_y, self.znear, self.zfar);
|
||||
|
||||
self.view_proj = OPENGL_TO_WGPU_MATRIX * proj;
|
||||
&self.view_proj
|
||||
|
||||
CameraUniform {
|
||||
view,
|
||||
inverse_projection: proj.inverse(),
|
||||
view_projection: self.view_proj,
|
||||
projection: proj,
|
||||
position,
|
||||
tile_debug: camera.debug as u32,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
use std::{collections::VecDeque, sync::Arc};
|
||||
|
||||
use tracing::instrument;
|
||||
|
||||
use super::{RenderGraphLabel, RenderGraphLabelValue};
|
||||
|
||||
/// A queued write to a GPU buffer targeting a graph slot.
|
||||
pub(crate) struct GraphBufferWrite {
|
||||
/// The name of the slot that has the resource that will be written
|
||||
pub(crate) target_slot: RenderGraphLabelValue,
|
||||
pub(crate) offset: u64,
|
||||
pub(crate) bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct RenderGraphContext<'a> {
|
||||
/// The [`wgpu::CommandEncoder`] used to encode GPU operations.
|
||||
///
|
||||
/// This is `None` during the `prepare` stage.
|
||||
pub encoder: Option<wgpu::CommandEncoder>,
|
||||
/// The gpu device that is being used.
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub queue: Arc<wgpu::Queue>,
|
||||
pub(crate) buffer_writes: VecDeque<GraphBufferWrite>,
|
||||
renderpass_desc: Vec<wgpu::RenderPassDescriptor<'a, 'a>>,
|
||||
/// The label of this Node.
|
||||
pub label: RenderGraphLabelValue,
|
||||
}
|
||||
|
||||
impl<'a> RenderGraphContext<'a> {
|
||||
pub(crate) fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, encoder: Option<wgpu::CommandEncoder>, label: RenderGraphLabelValue) -> Self {
|
||||
Self {
|
||||
encoder,
|
||||
device,
|
||||
queue,
|
||||
buffer_writes: Default::default(),
|
||||
renderpass_desc: vec![],
|
||||
label,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn begin_render_pass(
|
||||
&'a mut self,
|
||||
desc: wgpu::RenderPassDescriptor<'a, 'a>,
|
||||
) -> wgpu::RenderPass {
|
||||
self.encoder
|
||||
.as_mut()
|
||||
.expect(
|
||||
"RenderGraphContext is missing a command encoder. This is likely \
|
||||
because you are trying to run render commands in the prepare stage.",
|
||||
)
|
||||
.begin_render_pass(&desc)
|
||||
}
|
||||
|
||||
pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass {
|
||||
self.encoder
|
||||
.as_mut()
|
||||
.expect(
|
||||
"RenderGraphContext is missing a command encoder. This is likely \
|
||||
because you are trying to run render commands in the prepare stage.",
|
||||
)
|
||||
.begin_compute_pass(desc)
|
||||
}
|
||||
|
||||
/// Queue a data write to a buffer at that is contained in `target_slot`.
|
||||
///
|
||||
/// This does not submit the data to the GPU immediately, or add it to the `wgpu::Queue`. The
|
||||
/// data will be submitted to the GPU queue right after the prepare stage for all passes
|
||||
/// is ran.
|
||||
#[instrument(skip(self, bytes), level="trace", fields(size = bytes.len()))]
|
||||
pub fn queue_buffer_write(&mut self, target_slot: impl RenderGraphLabel, offset: u64, bytes: &[u8]) {
|
||||
self.buffer_writes.push_back(GraphBufferWrite {
|
||||
target_slot: target_slot.into(),
|
||||
offset,
|
||||
bytes: bytes.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Queue a data write of a type that to a buffer at that is contained in `target_slot`.
|
||||
#[instrument(skip(self, bytes), level="trace", fields(size = std::mem::size_of::<T>()))]
|
||||
pub fn queue_buffer_write_with<T: bytemuck::NoUninit>(
|
||||
&mut self,
|
||||
target_slot: impl RenderGraphLabel,
|
||||
offset: u64,
|
||||
bytes: T,
|
||||
) {
|
||||
self.queue_buffer_write(target_slot, offset, bytemuck::bytes_of(&bytes));
|
||||
}
|
||||
|
||||
/// Submit the encoder to the gpu queue.
|
||||
///
|
||||
/// The `encoder` of this context will be `None` until the next node is executed, then another
|
||||
/// one will be made. You likely don't need to run this yourself until you are manually
|
||||
/// presenting a surface texture.
|
||||
pub fn submit_encoder(&mut self) {
|
||||
let en = self.encoder.take()
|
||||
.unwrap()
|
||||
.finish();
|
||||
self.queue.submit(std::iter::once(en));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,572 @@
|
|||
mod node;
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut}, collections::VecDeque, fmt::Debug, hash::Hash, rc::Rc, sync::Arc
|
||||
};
|
||||
|
||||
use lyra_ecs::World;
|
||||
pub use node::*;
|
||||
|
||||
mod passes;
|
||||
pub use passes::*;
|
||||
|
||||
mod slot_desc;
|
||||
pub use slot_desc::*;
|
||||
|
||||
mod context;
|
||||
pub use context::*;
|
||||
|
||||
mod render_target;
|
||||
pub use render_target::*;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use tracing::{debug_span, instrument, trace, warn};
|
||||
use wgpu::CommandEncoder;
|
||||
|
||||
use super::resource::{ComputePipeline, Pass, Pipeline, RenderPipeline};
|
||||
|
||||
/// A trait that represents the label of a resource, slot, or node in the [`RenderGraph`].
|
||||
pub trait RenderGraphLabel: Debug + 'static {
|
||||
fn rc_clone(&self) -> Rc<dyn RenderGraphLabel>;
|
||||
fn as_label_hash(&self) -> u64;
|
||||
|
||||
fn label_eq_rc(&self, other: &Rc<dyn RenderGraphLabel>) -> bool {
|
||||
self.as_label_hash() == other.as_label_hash()
|
||||
}
|
||||
|
||||
fn label_eq(&self, other: &dyn RenderGraphLabel) -> bool {
|
||||
self.as_label_hash() == other.as_label_hash()
|
||||
}
|
||||
}
|
||||
|
||||
/// An owned [`RenderGraphLabel`].
|
||||
#[derive(Clone)]
|
||||
pub struct RenderGraphLabelValue(Rc<dyn RenderGraphLabel>);
|
||||
|
||||
impl Debug for RenderGraphLabelValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<L: RenderGraphLabel> From<L> for RenderGraphLabelValue {
|
||||
fn from(value: L) -> Self {
|
||||
Self(Rc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rc<dyn RenderGraphLabel>> for RenderGraphLabelValue {
|
||||
fn from(value: Rc<dyn RenderGraphLabel>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Rc<dyn RenderGraphLabel>> for RenderGraphLabelValue {
|
||||
fn from(value: &Rc<dyn RenderGraphLabel>) -> Self {
|
||||
Self(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for RenderGraphLabelValue {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write_u64(self.0.as_label_hash());
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for RenderGraphLabelValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.label_eq_rc(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for RenderGraphLabelValue {}
|
||||
|
||||
struct NodeEntry {
|
||||
/// The Node
|
||||
inner: Arc<RefCell<dyn Node>>,
|
||||
/// The Node descriptor
|
||||
desc: Rc<RefCell<NodeDesc>>,
|
||||
/// The index of the node in the execution graph
|
||||
graph_index: petgraph::matrix_graph::NodeIndex<usize>,
|
||||
/// The Node's optional pipeline
|
||||
pipeline: Rc<RefCell<Option<Pipeline>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct BindGroupEntry {
|
||||
label: RenderGraphLabelValue,
|
||||
/// BindGroup
|
||||
bg: Arc<wgpu::BindGroup>,
|
||||
/// BindGroupLayout
|
||||
layout: Option<Arc<wgpu::BindGroupLayout>>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone)]
|
||||
struct ResourceSlot {
|
||||
label: RenderGraphLabelValue,
|
||||
ty: SlotType,
|
||||
value: SlotValue,
|
||||
}
|
||||
|
||||
pub struct RenderGraph {
|
||||
device: Arc<wgpu::Device>,
|
||||
queue: Arc<wgpu::Queue>,
|
||||
slots: FxHashMap<RenderGraphLabelValue, ResourceSlot>,
|
||||
nodes: FxHashMap<RenderGraphLabelValue, NodeEntry>,
|
||||
sub_graphs: FxHashMap<RenderGraphLabelValue, RenderGraph>,
|
||||
bind_groups: FxHashMap<RenderGraphLabelValue, BindGroupEntry>,
|
||||
/// A directed graph used to determine dependencies of nodes.
|
||||
node_graph: petgraph::matrix_graph::DiMatrix<RenderGraphLabelValue, (), Option<()>, usize>,
|
||||
view_target: Rc<RefCell<ViewTarget>>,
|
||||
shader_prepoc: wgsl_preprocessor::Processor,
|
||||
}
|
||||
|
||||
impl RenderGraph {
|
||||
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, view_target: Rc<RefCell<ViewTarget>>) -> Self {
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
slots: Default::default(),
|
||||
nodes: Default::default(),
|
||||
sub_graphs: Default::default(),
|
||||
bind_groups: Default::default(),
|
||||
node_graph: Default::default(),
|
||||
view_target,
|
||||
shader_prepoc: wgsl_preprocessor::Processor::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device(&self) -> &wgpu::Device {
|
||||
&self.device
|
||||
}
|
||||
|
||||
/// Add a [`Node`] to the RenderGraph.
|
||||
///
|
||||
/// When the node is added, its [`Node::desc`] method will be executed.
|
||||
///
|
||||
/// Additionally, all [`Slot`](node::NodeSlot)s of the node will be iterated,
|
||||
/// 1. Ensuring that there are no two slots of the same name, with different value types
|
||||
/// 2. Changing the id of insert slots to match the id of the output slot of the same name.
|
||||
/// * This means that the id of insert slots **ARE NOT STABLE**. **DO NOT** rely on them to
|
||||
/// not change. The IDs of output slots do stay the same.
|
||||
/// 3. Ensuring that no two slots share the same ID when the names do not match.
|
||||
#[instrument(skip(self, node), level = "debug")]
|
||||
pub fn add_node<P: Node>(&mut self, label: impl RenderGraphLabel, mut node: P) {
|
||||
let mut desc = node.desc(self);
|
||||
|
||||
// collect all the slots of the node
|
||||
for slot in &mut desc.slots {
|
||||
if let Some(other) = self
|
||||
.slots
|
||||
.get_mut(&slot.label)
|
||||
{
|
||||
debug_assert_eq!(
|
||||
slot.ty, other.ty,
|
||||
"slot {:?} in node {:?} does not match existing slot of same name",
|
||||
slot.label, label
|
||||
);
|
||||
} else {
|
||||
debug_assert!(!self.slots.contains_key(&slot.label),
|
||||
"Reuse of id detected in render graph! Node: {:?}, slot: {:?}",
|
||||
label, slot.label,
|
||||
);
|
||||
|
||||
let res_slot = ResourceSlot {
|
||||
label: slot.label.clone(),
|
||||
ty: slot.ty,
|
||||
value: slot.value.clone().unwrap_or(SlotValue::None),
|
||||
};
|
||||
|
||||
self.slots.insert(slot.label.clone(), res_slot);
|
||||
}
|
||||
}
|
||||
|
||||
// get clones of the bind groups and layouts
|
||||
for (label, bg, bgl) in &desc.bind_groups {
|
||||
self.bind_groups.insert(label.clone(), BindGroupEntry {
|
||||
label: label.clone(),
|
||||
bg: bg.clone(),
|
||||
layout: bgl.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
let label: RenderGraphLabelValue = label.into();
|
||||
let index = self.node_graph.add_node(label.clone());
|
||||
|
||||
self.nodes.insert(
|
||||
label,
|
||||
NodeEntry {
|
||||
inner: Arc::new(RefCell::new(node)),
|
||||
desc: Rc::new(RefCell::new(desc)),
|
||||
graph_index: index,
|
||||
pipeline: Rc::new(RefCell::new(None)),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates all buffers required for the nodes.
|
||||
///
|
||||
/// This only needs to be ran when the [`Node`]s in the graph change, or they are removed or
|
||||
/// added.
|
||||
#[instrument(skip(self, device))]
|
||||
pub fn setup(&mut self, device: &wgpu::Device) {
|
||||
// For all nodes, create their pipelines
|
||||
for node in self.nodes.values_mut() {
|
||||
let desc = (*node.desc).borrow();
|
||||
if let Some(pipeline_desc) = &desc.pipeline_desc {
|
||||
let pipeline = match desc.ty {
|
||||
NodeType::Render => Pipeline::Render(RenderPipeline::create(
|
||||
device,
|
||||
pipeline_desc
|
||||
.as_render_pipeline_descriptor()
|
||||
.expect("got compute pipeline descriptor in a render node"),
|
||||
)),
|
||||
NodeType::Compute => Pipeline::Compute(ComputePipeline::create(
|
||||
device,
|
||||
pipeline_desc
|
||||
.as_compute_pipeline_descriptor()
|
||||
.expect("got render pipeline descriptor in a compute node"),
|
||||
)),
|
||||
NodeType::Presenter | NodeType::Node | NodeType::Graph => {
|
||||
panic!("Present or Node RenderGraph nodes should not have a pipeline descriptor!");
|
||||
},
|
||||
};
|
||||
|
||||
drop(desc);
|
||||
|
||||
let mut node_pipeline = node.pipeline.borrow_mut();
|
||||
*node_pipeline = Some(pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
for sub in self.sub_graphs.values_mut() {
|
||||
sub.setup(device);
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(self, world))]
|
||||
pub fn prepare(&mut self, world: &mut World) {
|
||||
let mut buffer_writes = VecDeque::<GraphBufferWrite>::new();
|
||||
// reserve some buffer writes. not all nodes write so half the amount of them is probably
|
||||
// fine.
|
||||
buffer_writes.reserve(self.nodes.len() / 2);
|
||||
|
||||
let mut sorted: VecDeque<RenderGraphLabelValue> = petgraph::algo::toposort(&self.node_graph, None)
|
||||
.expect("RenderGraph had cycled!")
|
||||
.iter()
|
||||
.map(|i| self.node_graph[*i].clone())
|
||||
.collect();
|
||||
|
||||
while let Some(node_label) = sorted.pop_front() {
|
||||
let node = self.nodes.get(&node_label).unwrap();
|
||||
let device = self.device.clone();
|
||||
let queue = self.queue.clone();
|
||||
|
||||
let inner = node.inner.clone();
|
||||
let mut inner = inner.borrow_mut();
|
||||
|
||||
let mut context = RenderGraphContext::new(device, queue, None, node_label.clone());
|
||||
inner.prepare(self, world, &mut context);
|
||||
buffer_writes.append(&mut context.buffer_writes);
|
||||
}
|
||||
|
||||
{
|
||||
// Queue all buffer writes to the gpu
|
||||
let s = debug_span!("queue_buffer_writes");
|
||||
let _e = s.enter();
|
||||
|
||||
while let Some(bufwr) = buffer_writes.pop_front() {
|
||||
let slot = self
|
||||
.slots
|
||||
.get(&bufwr.target_slot)
|
||||
.unwrap_or_else(|| panic!("Failed to find slot '{:?}' for buffer write",
|
||||
bufwr.target_slot));
|
||||
let buf = slot
|
||||
.value
|
||||
.as_buffer()
|
||||
.unwrap_or_else(|| panic!("Slot '{:?}' is not a buffer", bufwr.target_slot));
|
||||
|
||||
self.queue.write_buffer(buf, bufwr.offset, &bufwr.bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_encoder(&self) -> CommandEncoder {
|
||||
self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("graph encoder"),
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub fn render(&mut self) {
|
||||
let mut sorted: VecDeque<RenderGraphLabelValue> = petgraph::algo::toposort(&self.node_graph, None)
|
||||
.expect("RenderGraph had cycled!")
|
||||
.iter()
|
||||
.map(|i| self.node_graph[*i].clone())
|
||||
.collect();
|
||||
|
||||
// A bit of 'encoder hot potato' is played using this.
|
||||
// Although the encoder is an option, its only an option so ownership of it can be given
|
||||
// to the context for the time of the node execution.
|
||||
// After the node is executed, the encoder is taken back. If the node is a presenter node,
|
||||
// the encoder will be submitted and a new one will be made.
|
||||
let mut encoder = Some(self.create_encoder());
|
||||
|
||||
while let Some(node_label) = sorted.pop_front() {
|
||||
let node = self.nodes.get(&node_label).unwrap();
|
||||
let node_inn = node.inner.clone();
|
||||
|
||||
let node_desc = node.desc.clone();
|
||||
let node_desc = (*node_desc).borrow();
|
||||
|
||||
// clone of the Rc's is required to appease the borrow checker
|
||||
let device = self.device.clone();
|
||||
let queue = self.queue.clone();
|
||||
|
||||
// create a new encoder if the last one was submitted
|
||||
if encoder.is_none() {
|
||||
encoder = Some(self.create_encoder());
|
||||
}
|
||||
|
||||
let mut context = RenderGraphContext::new(device, queue, encoder.take(), node_label.clone());
|
||||
|
||||
trace!("Executing {:?}", node_label.0);
|
||||
let mut inner = node_inn.borrow_mut();
|
||||
inner.execute(self, &node_desc, &mut context);
|
||||
|
||||
// take back the encoder from the context
|
||||
encoder = context.encoder;
|
||||
}
|
||||
|
||||
if let Some(encoder) = encoder {
|
||||
self.queue.submit(std::iter::once(encoder.finish()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slot_value<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&SlotValue> {
|
||||
self.slots.get(&label.into()).map(|s| &s.value)
|
||||
}
|
||||
|
||||
pub fn slot_value_mut<L: Into<RenderGraphLabelValue>>(&mut self, label: L) -> Option<&mut SlotValue> {
|
||||
self.slots.get_mut(&label.into()).map(|s| &mut s.value)
|
||||
}
|
||||
|
||||
pub fn node_desc<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<Ref<NodeDesc>> {
|
||||
self.nodes.get(&label.into()).map(|s| (*s.desc).borrow())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn pipeline<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<Ref<Pipeline>> {
|
||||
self.nodes.get(&label.into())
|
||||
.and_then(|p| {
|
||||
let v = p.pipeline.borrow();
|
||||
|
||||
#[allow(clippy::manual_map)]
|
||||
match &*v {
|
||||
Some(_) => Some(Ref::map(v, |p| p.as_ref().unwrap())),
|
||||
None => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn try_bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Arc<wgpu::BindGroup>> {
|
||||
self.bind_groups.get(&label.into()).map(|e| &e.bg)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bind_group<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Arc<wgpu::BindGroup> {
|
||||
let l = label.into();
|
||||
self.try_bind_group(l.clone()).unwrap_or_else(|| panic!("Unknown label '{:?}' for bind group layout", l.clone()))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn try_bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> Option<&Arc<wgpu::BindGroupLayout>> {
|
||||
self.bind_groups.get(&label.into()).and_then(|e| e.layout.as_ref())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bind_group_layout<L: Into<RenderGraphLabelValue>>(&self, label: L) -> &Arc<wgpu::BindGroupLayout> {
|
||||
let l = label.into();
|
||||
self.try_bind_group_layout(l.clone())
|
||||
.unwrap_or_else(|| panic!("Unknown label '{:?}' for bind group layout", l.clone()))
|
||||
}
|
||||
|
||||
pub fn add_edge(&mut self, from: impl RenderGraphLabel, to: impl RenderGraphLabel)
|
||||
{
|
||||
let from = RenderGraphLabelValue::from(from);
|
||||
let to = RenderGraphLabelValue::from(to);
|
||||
|
||||
let from_idx = self
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|p| *p.0 == from)
|
||||
.map(|p| p.1.graph_index)
|
||||
.expect("Failed to find from node");
|
||||
let to_idx = self
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|p| *p.0 == to)
|
||||
.map(|p| p.1.graph_index)
|
||||
.expect("Failed to find to node");
|
||||
|
||||
debug_assert_ne!(from_idx, to_idx, "cannot add edges between the same node");
|
||||
|
||||
self.node_graph.add_edge(from_idx, to_idx, ());
|
||||
}
|
||||
|
||||
/// Utility method for setting the bind groups for a node.
|
||||
///
|
||||
/// The parameter `bind_groups` can be used to specify the labels of a bind group, and the
|
||||
/// index of the bind group in the pipeline for the node. If a bind group of the provided
|
||||
/// name is not found in the graph, a panic will occur.
|
||||
///
|
||||
/// # Example:
|
||||
/// ```nobuild
|
||||
/// graph.set_bind_groups(
|
||||
/// &mut pass,
|
||||
/// &[
|
||||
/// // retrieves the `BasePassSlots::DepthTexture` bind group and sets the index 0 in the
|
||||
/// // node to it.
|
||||
/// (&BaseNodeSlots::DepthTexture, 0),
|
||||
/// (&BaseNodeSlots::Camera, 1),
|
||||
/// (&LightBaseNodeSlots::Lights, 2),
|
||||
/// (&LightCullComputeNodeSlots::LightIndicesGridGroup, 3),
|
||||
/// (&BaseNodeSlots::ScreenSize, 4),
|
||||
/// ],
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if a bind group of a provided name is not found.
|
||||
pub fn set_bind_groups<'a, P: Pass<'a>>(
|
||||
&'a self,
|
||||
pass: &mut P,
|
||||
bind_groups: &[(&dyn RenderGraphLabel, u32)],
|
||||
) {
|
||||
for (label, index) in bind_groups {
|
||||
let bg = self
|
||||
.bind_group(label.rc_clone());
|
||||
|
||||
pass.set_bind_group(*index, bg, &[]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sub_graph_mut<L: Into<RenderGraphLabelValue>>(&mut self, label: L) -> Option<&mut RenderGraph> {
|
||||
self.sub_graphs.get_mut(&label.into())
|
||||
}
|
||||
|
||||
/// Add a sub graph.
|
||||
///
|
||||
/// > Note: the sub graph is not ran unless you add a node that executes it. See [`SubGraphNode`].
|
||||
pub fn add_sub_graph<L: Into<RenderGraphLabelValue>>(&mut self, label: L, sub: RenderGraph) {
|
||||
self.sub_graphs.insert(label.into(), sub);
|
||||
}
|
||||
|
||||
/// Clone rendering resources (slots, bind groups, etc.) to a sub graph.
|
||||
fn clone_resources_to_sub(&mut self, sub_graph: RenderGraphLabelValue, slots: Vec<RenderGraphLabelValue>) {
|
||||
// instead of inserting the slots to the sub graph as they are extracted from the parent graph,
|
||||
// they are done separately to make the borrow checker happy. If this is not done,
|
||||
// the borrow checker complains about multiple mutable borrows (or an inmutable borrow
|
||||
// while mutable borrowing) to self; caused by borrowing the sub graph from self, and
|
||||
// self.slots.
|
||||
let mut collected_slots = VecDeque::new();
|
||||
let mut collected_bind_groups = VecDeque::new();
|
||||
|
||||
for slot in slots.iter() {
|
||||
let mut found_res = false;
|
||||
|
||||
// Since slots and bind groups may go by the same label,
|
||||
// there must be a way to collect both of them. A flag variable is used to detect
|
||||
// if neither was found.
|
||||
|
||||
if let Some(slot_res) = self.slots.get(slot) {
|
||||
collected_slots.push_back(slot_res.clone());
|
||||
found_res = true;
|
||||
}
|
||||
|
||||
if let Some(bg_res) = self.bind_groups.get(slot) {
|
||||
collected_bind_groups.push_back(bg_res.clone());
|
||||
found_res = true;
|
||||
}
|
||||
|
||||
if !found_res {
|
||||
panic!("sub graph is missing {:?} input slot or bind group", slot);
|
||||
}
|
||||
}
|
||||
|
||||
let sg = self.sub_graph_mut(sub_graph.clone()).unwrap();
|
||||
while let Some(res) = collected_slots.pop_front() {
|
||||
sg.slots.insert(res.label.clone(), res);
|
||||
}
|
||||
|
||||
while let Some(bg) = collected_bind_groups.pop_front() {
|
||||
sg.bind_groups.insert(bg.label.clone(), bg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view_target(&self) -> Ref<ViewTarget> {
|
||||
self.view_target.borrow()
|
||||
}
|
||||
|
||||
pub fn view_target_mut(&self) -> RefMut<ViewTarget> {
|
||||
self.view_target.borrow_mut()
|
||||
}
|
||||
|
||||
/// Register a shader with the preprocessor.
|
||||
///
|
||||
/// This step also parses the shader and will return errors if it failed to parse.
|
||||
///
|
||||
/// Returns: The shader module import path if the module specified one.
|
||||
#[inline(always)]
|
||||
pub fn register_shader(&mut self, shader_src: &str) -> Result<Option<String>, wgsl_preprocessor::Error> {
|
||||
self.shader_prepoc.parse_module(shader_src)
|
||||
}
|
||||
|
||||
/// Preprocess a shader, returning the source.
|
||||
#[inline(always)]
|
||||
pub fn preprocess_shader(&mut self, shader_path: &str) -> Result<String, wgsl_preprocessor::Error> {
|
||||
self.shader_prepoc.preprocess_module(shader_path)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SubGraphNode {
|
||||
subg: RenderGraphLabelValue,
|
||||
slots: Vec<RenderGraphLabelValue>,
|
||||
}
|
||||
|
||||
impl SubGraphNode {
|
||||
pub fn new<L: Into<RenderGraphLabelValue>>(sub_label: L, slot_labels: Vec<RenderGraphLabelValue>) -> Self {
|
||||
Self {
|
||||
subg: sub_label.into(),
|
||||
slots: slot_labels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for SubGraphNode {
|
||||
fn desc(&mut self, _: &mut RenderGraph) -> NodeDesc {
|
||||
NodeDesc::new(NodeType::Graph, None, vec![])
|
||||
}
|
||||
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, _: &mut RenderGraphContext) {
|
||||
graph.clone_resources_to_sub(self.subg.clone(), self.slots.clone());
|
||||
|
||||
let sg = graph.sub_graph_mut(self.subg.clone())
|
||||
.unwrap_or_else(|| panic!("failed to find sub graph for SubGraphNode: {:?}", self.subg));
|
||||
sg.prepare(world);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut RenderGraph,
|
||||
_: &NodeDesc,
|
||||
_: &mut RenderGraphContext,
|
||||
) {
|
||||
graph.clone_resources_to_sub(self.subg.clone(), self.slots.clone());
|
||||
|
||||
let sg = graph.sub_graph_mut(self.subg.clone())
|
||||
.unwrap_or_else(|| panic!("failed to find sub graph for SubGraphNode: {:?}", self.subg));
|
||||
sg.render();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,387 @@
|
|||
use std::{cell::{Ref, RefCell, RefMut}, num::NonZeroU32, rc::Rc, sync::Arc};
|
||||
|
||||
use bind_match::bind_match;
|
||||
use lyra_ecs::World;
|
||||
|
||||
use crate::render::resource::PipelineDescriptor;
|
||||
|
||||
use super::{Frame, RenderGraph, RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderTarget};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||
pub enum NodeType {
|
||||
/// A node doesn't render, compute, or present anything. This likely means it injects data into the graph.
|
||||
#[default]
|
||||
Node,
|
||||
/// A Compute pass node type.
|
||||
Compute,
|
||||
/// A render pass node type.
|
||||
Render,
|
||||
/// A node that presents render results to a render target.
|
||||
Presenter,
|
||||
/// A node that represents a sub-graph.
|
||||
Graph,
|
||||
}
|
||||
|
||||
impl NodeType {
|
||||
/// Returns a boolean indicating if the node should have a [`Pipeline`](crate::render::resource::Pipeline).
|
||||
pub fn should_have_pipeline(&self) -> bool {
|
||||
match self {
|
||||
NodeType::Node => false,
|
||||
NodeType::Compute => true,
|
||||
NodeType::Render => true,
|
||||
NodeType::Presenter => false,
|
||||
NodeType::Graph => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of data that is stored in a [`Node`] slot.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SlotType {
|
||||
TextureView,
|
||||
Sampler,
|
||||
Texture,
|
||||
Buffer,
|
||||
RenderTarget,
|
||||
Frame,
|
||||
}
|
||||
|
||||
/// The value of a slot in a [`Node`].
|
||||
#[derive(Clone)]
|
||||
pub enum SlotValue {
|
||||
/// This slot doesn't have any value
|
||||
None,
|
||||
/// The value will be set during a later phase of the render graph. To see the type of value
|
||||
/// this will be set to, see the slots type.
|
||||
Lazy,
|
||||
TextureView(Arc<wgpu::TextureView>),
|
||||
Sampler(Rc<wgpu::Sampler>),
|
||||
Texture(Arc<wgpu::Texture>),
|
||||
Buffer(Arc<wgpu::Buffer>),
|
||||
RenderTarget(Rc<RefCell<RenderTarget>>),
|
||||
Frame(Rc<RefCell<Option<Frame>>>),
|
||||
}
|
||||
|
||||
impl SlotValue {
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, Self::None)
|
||||
}
|
||||
|
||||
pub fn is_lazy(&self) -> bool {
|
||||
matches!(self, Self::Lazy)
|
||||
}
|
||||
|
||||
pub fn as_texture_view(&self) -> Option<&Arc<wgpu::TextureView>> {
|
||||
bind_match!(self, Self::TextureView(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_sampler(&self) -> Option<&Rc<wgpu::Sampler>> {
|
||||
bind_match!(self, Self::Sampler(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_texture(&self) -> Option<&Arc<wgpu::Texture>> {
|
||||
bind_match!(self, Self::Texture(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_buffer(&self) -> Option<&Arc<wgpu::Buffer>> {
|
||||
bind_match!(self, Self::Buffer(v) => v)
|
||||
}
|
||||
|
||||
pub fn as_render_target(&self) -> Option<Ref<RenderTarget>> {
|
||||
bind_match!(self, Self::RenderTarget(v) => v.borrow())
|
||||
}
|
||||
|
||||
pub fn as_render_target_mut(&mut self) -> Option<RefMut<RenderTarget>> {
|
||||
bind_match!(self, Self::RenderTarget(v) => v.borrow_mut())
|
||||
}
|
||||
|
||||
pub fn as_frame(&self) -> Option<Ref<Option<Frame>>> {
|
||||
bind_match!(self, Self::Frame(v) => v.borrow())
|
||||
}
|
||||
|
||||
pub fn as_frame_mut(&mut self) -> Option<RefMut<Option<Frame>>> {
|
||||
bind_match!(self, Self::Frame(v) => v.borrow_mut())
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SlotAttribute {
|
||||
/// This slot inputs a value into the node, expecting another node to `Output` it.
|
||||
Input,
|
||||
/// This slot outputs a value from the node, providing the value to other nodes that
|
||||
/// `Input`it.
|
||||
Output,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NodeSlot {
|
||||
/// The type of the value that this slot inputs/outputs.
|
||||
pub ty: SlotType,
|
||||
/// The way this slot uses the value. Defines if this slot is an output or input.
|
||||
pub attribute: SlotAttribute,
|
||||
/// The identifying label of this slot.
|
||||
pub label: RenderGraphLabelValue,
|
||||
/// The value of the slot.
|
||||
/// This is `None` if the slot is a `SlotAttribute::Input` type.
|
||||
pub value: Option<SlotValue>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PipelineShaderDesc {
|
||||
pub label: Option<String>,
|
||||
pub source: String,
|
||||
pub entry_point: String,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RenderGraphPipelineInfo {
|
||||
/// Debug label of the pipeline. This will show up in graphics debuggers for easy identification.
|
||||
pub label: Option<String>,
|
||||
/// The layout of bind groups for this pipeline.
|
||||
pub bind_group_layouts: Vec<Rc<wgpu::BindGroupLayout>>,
|
||||
/// The descriptor of the vertex shader.
|
||||
pub vertex: PipelineShaderDesc,
|
||||
/// The properties of the pipeline at the primitive assembly and rasterization level.
|
||||
pub primitive: wgpu::PrimitiveState,
|
||||
/// The effect of draw calls on the depth and stencil aspects of the output target, if any.
|
||||
pub depth_stencil: Option<wgpu::DepthStencilState>,
|
||||
/// The multi-sampling properties of the pipeline.
|
||||
pub multisample: wgpu::MultisampleState,
|
||||
/// The compiled fragment stage, its entry point, and the color targets.
|
||||
pub fragment: Option<PipelineShaderDesc>,
|
||||
/// If the pipeline will be used with a multiview render pass, this indicates how many array
|
||||
/// layers the attachments will have.
|
||||
pub multiview: Option<NonZeroU32>,
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
impl RenderGraphPipelineInfo {
|
||||
pub fn new(
|
||||
label: &str,
|
||||
bind_group_layouts: Vec<wgpu::BindGroupLayout>,
|
||||
vertex: PipelineShaderDesc,
|
||||
primitive: wgpu::PrimitiveState,
|
||||
depth_stencil: Option<wgpu::DepthStencilState>,
|
||||
multisample: wgpu::MultisampleState,
|
||||
fragment: Option<PipelineShaderDesc>,
|
||||
multiview: Option<NonZeroU32>,
|
||||
) -> Self {
|
||||
Self {
|
||||
label: Some(label.to_string()),
|
||||
bind_group_layouts: bind_group_layouts
|
||||
.into_iter()
|
||||
.map(Rc::new)
|
||||
.collect(),
|
||||
vertex,
|
||||
primitive,
|
||||
depth_stencil,
|
||||
multisample,
|
||||
fragment,
|
||||
multiview,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Descriptor of a Node in a [`RenderGraph`].
|
||||
pub struct NodeDesc {
|
||||
/// The [`NodeType`] of the node.
|
||||
pub ty: NodeType,
|
||||
/// The slots that the Node uses.
|
||||
/// This defines the resources that the node uses and creates in the graph.
|
||||
pub slots: Vec<NodeSlot>,
|
||||
//slot_label_lookup: HashMap<RenderGraphLabelValue, u64>,
|
||||
/// An optional pipeline descriptor for the Node.
|
||||
/// This is `None` if the Node type is not a node that requires a pipeline
|
||||
/// (see [`NodeType::should_have_pipeline`]).
|
||||
pub pipeline_desc: Option<PipelineDescriptor>,
|
||||
/// The bind groups that this Node creates.
|
||||
/// This makes the bind groups accessible to other Nodes.
|
||||
pub bind_groups: Vec<(
|
||||
RenderGraphLabelValue,
|
||||
Arc<wgpu::BindGroup>,
|
||||
Option<Arc<wgpu::BindGroupLayout>>,
|
||||
)>,
|
||||
}
|
||||
|
||||
impl NodeDesc {
|
||||
/// Create a new node descriptor.
|
||||
pub fn new(
|
||||
pass_type: NodeType,
|
||||
pipeline_desc: Option<PipelineDescriptor>,
|
||||
bind_groups: Vec<(&dyn RenderGraphLabel, Arc<wgpu::BindGroup>, Option<Arc<wgpu::BindGroupLayout>>)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
ty: pass_type,
|
||||
slots: vec![],
|
||||
pipeline_desc,
|
||||
bind_groups: bind_groups
|
||||
.into_iter()
|
||||
.map(|bg| (bg.0.rc_clone().into(), bg.1, bg.2))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a slot to the descriptor.
|
||||
///
|
||||
/// In debug builds, there is an assert that triggers if the slot is an input slot and has
|
||||
/// a value set.
|
||||
pub fn add_slot(&mut self, slot: NodeSlot) {
|
||||
debug_assert!(
|
||||
!(slot.attribute == SlotAttribute::Input && slot.value.is_some()),
|
||||
"input slots should not have values"
|
||||
);
|
||||
|
||||
self.slots.push(slot);
|
||||
}
|
||||
|
||||
/// Add a buffer slot to the descriptor.
|
||||
///
|
||||
/// In debug builds, there is an assert that triggers if the slot is an input slot and has
|
||||
/// a value set. There is also an assert that is triggered if this slot value is not `None`,
|
||||
/// `SlotValue::Lazy` or a `Buffer`.
|
||||
#[inline(always)]
|
||||
pub fn add_buffer_slot<L: RenderGraphLabel>(
|
||||
&mut self,
|
||||
label: L,
|
||||
attribute: SlotAttribute,
|
||||
value: Option<SlotValue>,
|
||||
) {
|
||||
debug_assert!(
|
||||
matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::Buffer(_))),
|
||||
"slot value is not a buffer"
|
||||
);
|
||||
|
||||
let slot = NodeSlot {
|
||||
label: label.into(),
|
||||
ty: SlotType::Buffer,
|
||||
attribute,
|
||||
value,
|
||||
};
|
||||
self.add_slot(slot);
|
||||
}
|
||||
|
||||
/// Add a slot that stores a [`wgpu::Texture`] to the descriptor.
|
||||
///
|
||||
/// In debug builds, there is an assert that triggers if the slot is an input slot and has
|
||||
/// a value set. There is also an assert that is triggered if this slot value is not `None`,
|
||||
/// `SlotValue::Lazy` or a `SlotValue::Texture`.
|
||||
#[inline(always)]
|
||||
pub fn add_texture_slot<L: RenderGraphLabel>(
|
||||
&mut self,
|
||||
label: L,
|
||||
attribute: SlotAttribute,
|
||||
value: Option<SlotValue>,
|
||||
) {
|
||||
debug_assert!(
|
||||
matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::Texture(_))),
|
||||
"slot value is not a texture"
|
||||
);
|
||||
|
||||
let slot = NodeSlot {
|
||||
label: label.into(),
|
||||
ty: SlotType::Texture,
|
||||
attribute,
|
||||
value,
|
||||
};
|
||||
self.add_slot(slot);
|
||||
}
|
||||
|
||||
/// Add a slot that stores a [`wgpu::TextureView`] to the descriptor.
|
||||
///
|
||||
/// In debug builds, there is an assert that triggers if the slot is an input slot and has
|
||||
/// a value set. There is also an assert that is triggered if this slot value is not `None`,
|
||||
/// `SlotValue::Lazy` or a `SlotValue::TextureView`.
|
||||
#[inline(always)]
|
||||
pub fn add_texture_view_slot<L: RenderGraphLabel>(
|
||||
&mut self,
|
||||
label: L,
|
||||
attribute: SlotAttribute,
|
||||
value: Option<SlotValue>,
|
||||
) {
|
||||
debug_assert!(
|
||||
matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::TextureView(_))),
|
||||
"slot value is not a texture view"
|
||||
);
|
||||
|
||||
let slot = NodeSlot {
|
||||
label: label.into(),
|
||||
ty: SlotType::TextureView,
|
||||
attribute,
|
||||
value,
|
||||
};
|
||||
self.add_slot(slot);
|
||||
}
|
||||
|
||||
/// Add a slot that stores a [`wgpu::Sampler`] to the descriptor.
|
||||
///
|
||||
/// In debug builds, there is an assert that triggers if the slot is an input slot and has
|
||||
/// a value set. There is also an assert that is triggered if this slot value is not `None`,
|
||||
/// `SlotValue::Lazy` or a `SlotValue::Sampler`.
|
||||
#[inline(always)]
|
||||
pub fn add_sampler_slot<L: RenderGraphLabel>(
|
||||
&mut self,
|
||||
label: L,
|
||||
attribute: SlotAttribute,
|
||||
value: Option<SlotValue>,
|
||||
) {
|
||||
debug_assert!(
|
||||
matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::Sampler(_))),
|
||||
"slot value is not a sampler"
|
||||
);
|
||||
|
||||
let slot = NodeSlot {
|
||||
label: label.into(),
|
||||
ty: SlotType::Sampler,
|
||||
attribute,
|
||||
value,
|
||||
};
|
||||
self.add_slot(slot);
|
||||
}
|
||||
|
||||
/// Returns all input slots that the descriptor defines.
|
||||
pub fn input_slots(&self) -> Vec<&NodeSlot> {
|
||||
self.slots
|
||||
.iter()
|
||||
.filter(|s| s.attribute == SlotAttribute::Input)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns all output slots that the descriptor defines.
|
||||
pub fn output_slots(&self) -> Vec<&NodeSlot> {
|
||||
self.slots
|
||||
.iter()
|
||||
.filter(|s| s.attribute == SlotAttribute::Output)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// A node that can be executed and scheduled in a [`RenderGraph`].
|
||||
///
|
||||
/// A node can be used for rendering, computing data on the GPU, collecting data from the main
|
||||
/// world and writing it to GPU buffers, or presenting renders to a surface.
|
||||
///
|
||||
/// The [`RenderGraph`] is ran in phases. The first phase is `prepare`, then `execute`. When a node
|
||||
/// is first added to a RenderGraph, its [`Node::desc`] function will be ran. The descriptor
|
||||
/// describes all resources the node requires for execution during the `execute` phase.
|
||||
pub trait Node: 'static {
|
||||
/// Retrieve a descriptor of the Node.
|
||||
fn desc(&mut self, graph: &mut RenderGraph) -> NodeDesc;
|
||||
|
||||
/// Prepare the node for rendering.
|
||||
///
|
||||
/// This phase runs before `execute` and is meant to be used to collect data from the World
|
||||
/// and write to GPU buffers.
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, context: &mut RenderGraphContext);
|
||||
|
||||
/// Execute the node.
|
||||
///
|
||||
/// Parameters:
|
||||
/// * `graph` - The RenderGraph that this node is a part of. Can be used to get bind groups and bind to them.
|
||||
/// * `desc` - The descriptor of this node.
|
||||
/// * `context` - The rendering graph context.
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut RenderGraph,
|
||||
desc: &NodeDesc,
|
||||
context: &mut RenderGraphContext,
|
||||
);
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use glam::UVec2;
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use tracing::warn;
|
||||
use winit::dpi::PhysicalSize;
|
||||
|
||||
use crate::{
|
||||
render::{
|
||||
camera::{CameraUniform, RenderCamera},
|
||||
graph::{
|
||||
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
||||
},
|
||||
render_buffer::BufferWrapper, texture::RenderTexture,
|
||||
},
|
||||
scene::CameraComponent,
|
||||
};
|
||||
|
||||
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
|
||||
pub struct BasePassLabel;
|
||||
|
||||
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
|
||||
pub enum BasePassSlots {
|
||||
DepthTexture,
|
||||
ScreenSize,
|
||||
Camera,
|
||||
DepthTextureView,
|
||||
}
|
||||
|
||||
/// Supplies some basic things other passes needs.
|
||||
///
|
||||
/// screen size buffer, camera buffer,
|
||||
#[derive(Default)]
|
||||
pub struct BasePass {
|
||||
screen_size: UVec2,
|
||||
}
|
||||
|
||||
impl BasePass {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for BasePass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let vt = graph.view_target();
|
||||
self.screen_size = vt.size();
|
||||
|
||||
let (screen_size_bgl, screen_size_bg, screen_size_buf, _) = BufferWrapper::builder()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
.label_prefix("ScreenSize")
|
||||
.visibility(wgpu::ShaderStages::COMPUTE)
|
||||
.buffer_dynamic_offset(false)
|
||||
.contents(&[self.screen_size])
|
||||
.finish_parts(graph.device());
|
||||
let screen_size_bgl = Arc::new(screen_size_bgl);
|
||||
let screen_size_bg = Arc::new(screen_size_bg);
|
||||
|
||||
let (camera_bgl, camera_bg, camera_buf, _) = BufferWrapper::builder()
|
||||
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||
.label_prefix("camera")
|
||||
.visibility(wgpu::ShaderStages::all())
|
||||
.buffer_dynamic_offset(false)
|
||||
.contents(&[CameraUniform::default()])
|
||||
.finish_parts(graph.device());
|
||||
let camera_bgl = Arc::new(camera_bgl);
|
||||
let camera_bg = Arc::new(camera_bg);
|
||||
|
||||
// create the depth texture using the utility struct, then take all the required fields
|
||||
let mut depth_texture = RenderTexture::create_depth_texture(graph.device(), self.screen_size, "depth_texture");
|
||||
depth_texture.create_bind_group(&graph.device);
|
||||
|
||||
let dt_bg_pair = depth_texture.bindgroup_pair.unwrap();
|
||||
let depth_texture_bg = Arc::new(dt_bg_pair.bindgroup);
|
||||
let depth_texture_bgl = dt_bg_pair.layout;
|
||||
let depth_texture_view = Arc::new(depth_texture.view);
|
||||
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeType::Node,
|
||||
None,
|
||||
vec![
|
||||
// TODO: Make this a trait maybe?
|
||||
// Could impl it for (RenderGraphLabel, wgpu::BindGroup) and also
|
||||
// (RenderGraphLabel, wgpu::BindGroup, wgpu::BindGroupLabel) AND
|
||||
// (RenderGraphLabel, wgpu::BindGroup, Option<wgpu::BindGroupLabel>)
|
||||
//
|
||||
// This could make it slightly easier to create this
|
||||
(&BasePassSlots::DepthTexture, depth_texture_bg, Some(depth_texture_bgl)),
|
||||
(&BasePassSlots::ScreenSize, screen_size_bg, Some(screen_size_bgl)),
|
||||
(&BasePassSlots::Camera, camera_bg, Some(camera_bgl)),
|
||||
],
|
||||
);
|
||||
|
||||
desc.add_texture_view_slot(
|
||||
BasePassSlots::DepthTextureView,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::TextureView(depth_texture_view)),
|
||||
);
|
||||
desc.add_buffer_slot(
|
||||
BasePassSlots::ScreenSize,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Arc::new(screen_size_buf))),
|
||||
);
|
||||
desc.add_buffer_slot(
|
||||
BasePassSlots::Camera,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Arc::new(camera_buf))),
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
if let Some(camera) = world.view_iter::<&mut CameraComponent>().next() {
|
||||
let screen_size = graph.view_target().size();
|
||||
|
||||
let mut render_cam =
|
||||
RenderCamera::new(PhysicalSize::new(screen_size.x, screen_size.y));
|
||||
let uniform = render_cam.calc_view_projection(&camera);
|
||||
|
||||
context.queue_buffer_write_with(BasePassSlots::Camera, 0, uniform)
|
||||
} else {
|
||||
warn!("Missing camera!");
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
_desc: &crate::render::graph::NodeDesc,
|
||||
context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
let mut vt = graph.view_target_mut();
|
||||
vt.primary.create_frame();
|
||||
vt.primary.create_frame_view();
|
||||
/* debug_assert!(
|
||||
!rt.current_texture.is_some(),
|
||||
"main render target surface was not presented!"
|
||||
); */
|
||||
|
||||
// update the screen size buffer if the size changed.
|
||||
let rt_size = vt.size();
|
||||
if rt_size != self.screen_size {
|
||||
self.screen_size = rt_size;
|
||||
context.queue_buffer_write_with(BasePassSlots::ScreenSize, 0, self.screen_size)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
use std::{collections::HashMap, rc::Rc, sync::Arc};
|
||||
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
||||
use crate::render::{
|
||||
graph::{Node, NodeDesc, NodeType},
|
||||
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState},
|
||||
};
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
|
||||
pub struct FxaaPassLabel;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FxaaPass {
|
||||
target_sampler: Option<wgpu::Sampler>,
|
||||
bgl: Option<Arc<wgpu::BindGroupLayout>>,
|
||||
/// Store bind groups for the input textures.
|
||||
/// The texture may change due to resizes, or changes to the view target chain
|
||||
/// from other nodes.
|
||||
bg_cache: HashMap<wgpu::Id, wgpu::BindGroup>,
|
||||
}
|
||||
|
||||
impl FxaaPass {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for FxaaPass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let device = &graph.device;
|
||||
|
||||
let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("fxaa_bgl"),
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
let bgl = Arc::new(bgl);
|
||||
self.bgl = Some(bgl.clone());
|
||||
self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("fxaa sampler"),
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
let shader = Rc::new(Shader {
|
||||
label: Some("fxaa_shader".into()),
|
||||
source: include_str!("../../shaders/fxaa.wgsl").to_string(),
|
||||
});
|
||||
|
||||
let vt = graph.view_target();
|
||||
|
||||
NodeDesc::new(
|
||||
NodeType::Render,
|
||||
Some(PipelineDescriptor::Render(RenderPipelineDescriptor {
|
||||
label: Some("fxaa_pass".into()),
|
||||
layouts: vec![bgl.clone()],
|
||||
push_constant_ranges: vec![],
|
||||
vertex: VertexState {
|
||||
module: shader.clone(),
|
||||
entry_point: "vs_main".into(),
|
||||
buffers: vec![],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
module: shader,
|
||||
entry_point: "fs_main".into(),
|
||||
targets: vec![Some(wgpu::ColorTargetState {
|
||||
format: vt.format(),
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
depth_stencil: None,
|
||||
primitive: wgpu::PrimitiveState::default(),
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
multiview: None,
|
||||
})),
|
||||
vec![],
|
||||
)
|
||||
}
|
||||
|
||||
fn prepare(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
_: &mut lyra_ecs::World,
|
||||
_: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
//todo!()
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
let pipeline = graph
|
||||
.pipeline(context.label.clone())
|
||||
.expect("Failed to find pipeline for FxaaPass");
|
||||
|
||||
let mut vt = graph.view_target_mut();
|
||||
let chain = vt.get_chain();
|
||||
let source_view = chain.source.frame_view.as_ref().unwrap();
|
||||
let dest_view = chain.dest.frame_view.as_ref().unwrap();
|
||||
|
||||
let bg = self
|
||||
.bg_cache
|
||||
.entry(source_view.global_id())
|
||||
.or_insert_with(|| {
|
||||
graph
|
||||
.device()
|
||||
.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("fxaa_bg"),
|
||||
layout: self.bgl.as_ref().unwrap(),
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(source_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::Sampler(
|
||||
self.target_sampler.as_ref().unwrap(),
|
||||
),
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
{
|
||||
let encoder = context.encoder.as_mut().unwrap();
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("fxaa_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: dest_view,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: true,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
});
|
||||
pass.set_pipeline(pipeline.as_render());
|
||||
|
||||
pass.set_bind_group(0, bg, &[]);
|
||||
pass.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
||||
use crate::render::graph::{Node, NodeDesc, NodeSlot, NodeType};
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Hash, RenderGraphLabel)]
|
||||
pub struct InitNodeLabel;
|
||||
|
||||
pub struct InitNode {
|
||||
slots: Vec<NodeSlot>,
|
||||
}
|
||||
|
||||
impl Node for InitNode {
|
||||
fn desc(&mut self, _: &mut crate::render::graph::RenderGraph) -> crate::render::graph::NodeDesc {
|
||||
let mut desc = NodeDesc::new(NodeType::Node, None, vec![]);
|
||||
// the slots can just be cloned since the slot attribute doesn't really matter much.
|
||||
desc.slots = self.slots.clone();
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, _: &mut crate::render::graph::RenderGraph, _: &mut lyra_ecs::World, _: &mut crate::render::graph::RenderGraphContext) {
|
||||
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
_: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
_: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
use lyra_game_derive::RenderGraphLabel;
|
||||
|
||||
use crate::render::{
|
||||
graph::{
|
||||
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
||||
},
|
||||
light::LightUniformBuffers,
|
||||
};
|
||||
|
||||
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
|
||||
pub struct LightBasePassLabel;
|
||||
|
||||
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
|
||||
pub enum LightBasePassSlots {
|
||||
Lights
|
||||
}
|
||||
|
||||
/// Supplies some basic things other passes needs.
|
||||
///
|
||||
/// screen size buffer, camera buffer,
|
||||
#[derive(Default)]
|
||||
pub struct LightBasePass {
|
||||
light_buffers: Option<LightUniformBuffers>,
|
||||
}
|
||||
|
||||
impl LightBasePass {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for LightBasePass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
let device = &graph.device;
|
||||
self.light_buffers = Some(LightUniformBuffers::new(device));
|
||||
let light_buffers = self.light_buffers.as_ref().unwrap();
|
||||
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeType::Node,
|
||||
None,
|
||||
vec![(
|
||||
&LightBasePassSlots::Lights,
|
||||
light_buffers.bind_group.clone(),
|
||||
Some(light_buffers.bind_group_layout.clone()),
|
||||
)],
|
||||
);
|
||||
|
||||
desc.add_buffer_slot(
|
||||
LightBasePassSlots::Lights,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(light_buffers.buffer.clone())),
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, _graph: &mut RenderGraph, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||
let tick = world.current_tick();
|
||||
let lights = self.light_buffers.as_mut().unwrap();
|
||||
lights.update_lights(&context.queue, tick, world);
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
_graph: &mut crate::render::graph::RenderGraph,
|
||||
_desc: &crate::render::graph::NodeDesc,
|
||||
_context: &mut crate::render::graph::RenderGraphContext,
|
||||
) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
use std::{mem, rc::Rc, sync::Arc};
|
||||
|
||||
use glam::Vec2Swizzles;
|
||||
use lyra_ecs::World;
|
||||
use lyra_game_derive::RenderGraphLabel;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
use crate::render::{
|
||||
graph::{
|
||||
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
|
||||
}, renderer::ScreenSize, resource::{ComputePipeline, ComputePipelineDescriptor, Shader}
|
||||
};
|
||||
|
||||
use super::{BasePassSlots, LightBasePassSlots};
|
||||
|
||||
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
|
||||
pub struct LightCullComputePassLabel;
|
||||
|
||||
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
|
||||
pub enum LightCullComputePassSlots {
|
||||
LightGridTexture,
|
||||
LightGridTextureView,
|
||||
IndexCounterBuffer,
|
||||
LightIndicesGridGroup,
|
||||
}
|
||||
|
||||
pub struct LightCullComputePass {
|
||||
workgroup_size: glam::UVec2,
|
||||
pipeline: Option<ComputePipeline>,
|
||||
}
|
||||
|
||||
impl LightCullComputePass {
|
||||
pub fn new(screen_size: winit::dpi::PhysicalSize<u32>) -> Self {
|
||||
Self {
|
||||
workgroup_size: glam::UVec2::new(screen_size.width, screen_size.height),
|
||||
pipeline: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for LightCullComputePass {
|
||||
fn desc(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
) -> crate::render::graph::NodeDesc {
|
||||
// initialize some buffers with empty data
|
||||
let mut contents = Vec::<u8>::new();
|
||||
let contents_len =
|
||||
self.workgroup_size.x * self.workgroup_size.y * mem::size_of::<u32>() as u32;
|
||||
contents.resize(contents_len as _, 0);
|
||||
|
||||
let device = graph.device();
|
||||
let light_indices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("light_indices_buffer"),
|
||||
contents: &contents,
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let light_index_counter_buffer =
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("light_index_counter_buffer"),
|
||||
contents: bytemuck::cast_slice(&[0]),
|
||||
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||
});
|
||||
|
||||
let light_indices_bg_layout = Arc::new(device.create_bind_group_layout(
|
||||
&wgpu::BindGroupLayoutDescriptor {
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: false },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::StorageTexture {
|
||||
access: wgpu::StorageTextureAccess::ReadWrite,
|
||||
format: wgpu::TextureFormat::Rg32Uint, // vec2<uint>
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::COMPUTE,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Storage { read_only: false },
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
label: Some("light_indices_grid_bgl"),
|
||||
},
|
||||
));
|
||||
|
||||
let size = wgpu::Extent3d {
|
||||
width: self.workgroup_size.x,
|
||||
height: self.workgroup_size.y,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
let grid_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("light_grid_tex"),
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rg32Uint, // vec2<uint>
|
||||
usage: wgpu::TextureUsages::STORAGE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
||||
let grid_texture_view = grid_texture.create_view(&wgpu::TextureViewDescriptor {
|
||||
label: Some("light_grid_texview"),
|
||||
format: Some(wgpu::TextureFormat::Rg32Uint), // vec2<uint>
|
||||
dimension: Some(wgpu::TextureViewDimension::D2),
|
||||
aspect: wgpu::TextureAspect::All,
|
||||
base_mip_level: 0,
|
||||
mip_level_count: None,
|
||||
base_array_layer: 0,
|
||||
array_layer_count: None,
|
||||
});
|
||||
|
||||
let light_indices_bg = Arc::new(device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
layout: &light_indices_bg_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &light_indices_buffer,
|
||||
offset: 0,
|
||||
size: None, // the entire light buffer is needed
|
||||
}),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(&grid_texture_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
||||
buffer: &light_index_counter_buffer,
|
||||
offset: 0,
|
||||
size: None, // the entire light buffer is needed
|
||||
}),
|
||||
},
|
||||
],
|
||||
label: Some("light_indices_grid_bind_group"),
|
||||
}));
|
||||
|
||||
//drop(main_rt);
|
||||
|
||||
/* let depth_tex_bgl = graph.bind_group_layout(BasePassSlots::DepthTexture);
|
||||
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera);
|
||||
let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights);
|
||||
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize); */
|
||||
|
||||
let mut desc = NodeDesc::new(
|
||||
NodeType::Compute,
|
||||
/* Some(PipelineDescriptor::Compute(ComputePipelineDescriptor {
|
||||
label: Some("light_cull_pipeline".into()),
|
||||
push_constant_ranges: vec![],
|
||||
layouts: vec![
|
||||
depth_tex_bgl.clone(),
|
||||
camera_bgl.clone(),
|
||||
lights_bgl.clone(),
|
||||
light_indices_bg_layout.clone(),
|
||||
screen_size_bgl.clone(),
|
||||
],
|
||||
shader,
|
||||
shader_entry_point: "cs_main".into(),
|
||||
})), */
|
||||
None,
|
||||
vec![(
|
||||
&LightCullComputePassSlots::LightIndicesGridGroup,
|
||||
light_indices_bg,
|
||||
Some(light_indices_bg_layout),
|
||||
)],
|
||||
);
|
||||
|
||||
desc.add_buffer_slot(
|
||||
BasePassSlots::ScreenSize,
|
||||
SlotAttribute::Input,
|
||||
None,
|
||||
);
|
||||
desc.add_buffer_slot(BasePassSlots::Camera, SlotAttribute::Input, None);
|
||||
desc.add_buffer_slot(
|
||||
LightCullComputePassSlots::IndexCounterBuffer,
|
||||
SlotAttribute::Output,
|
||||
Some(SlotValue::Buffer(Arc::new(light_index_counter_buffer))),
|
||||
);
|
||||
|
||||
desc
|
||||
}
|
||||
|
||||
fn prepare(&mut self, graph: &mut RenderGraph, world: &mut World, context: &mut RenderGraphContext) {
|
||||
context.queue_buffer_write_with(LightCullComputePassSlots::IndexCounterBuffer, 0, 0);
|
||||
|
||||
let screen_size = world.get_resource::<ScreenSize>();
|
||||
if screen_size.xy() != self.workgroup_size {
|
||||
self.workgroup_size = screen_size.xy();
|
||||
todo!("Resize buffers and other resources");
|
||||
}
|
||||
|
||||
if self.pipeline.is_none() {
|
||||
let device = graph.device();
|
||||
|
||||
let depth_tex_bgl = graph.bind_group_layout(BasePassSlots::DepthTexture);
|
||||
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera);
|
||||
let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights);
|
||||
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize);
|
||||
let light_indices_bg_layout = graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
|
||||
|
||||
let shader = Rc::new(Shader {
|
||||
label: Some("light_cull_comp_shader".into()),
|
||||
source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(),
|
||||
});
|
||||
|
||||
let pipeline = ComputePipeline::create(device, &ComputePipelineDescriptor {
|
||||
label: Some("light_cull_pipeline".into()),
|
||||
push_constant_ranges: vec![],
|
||||
layouts: vec![
|
||||
depth_tex_bgl.clone(),
|
||||
camera_bgl.clone(),
|
||||
lights_bgl.clone(),
|
||||
light_indices_bg_layout.clone(),
|
||||
screen_size_bgl.clone(),
|
||||
],
|
||||
shader,
|
||||
shader_entry_point: "cs_main".into(),
|
||||
});
|
||||
|
||||
self.pipeline = Some(pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&mut self,
|
||||
graph: &mut crate::render::graph::RenderGraph,
|
||||
_: &crate::render::graph::NodeDesc,
|
||||
context: &mut RenderGraphContext,
|
||||
) {
|
||||
let pipeline = self.pipeline.as_mut().unwrap();
|
||||
|
||||
let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor {
|
||||
label: Some("light_cull_pass"),
|
||||
});
|
||||
|
||||
pass.set_pipeline(pipeline);
|
||||
|
||||
graph.set_bind_groups(
|
||||
&mut pass,
|
||||
&[
|
||||
(&BasePassSlots::DepthTexture, 0),
|
||||
(&BasePassSlots::Camera, 1),
|
||||
(&LightBasePassSlots::Lights, 2),
|
||||
(&LightCullComputePassSlots::LightIndicesGridGroup, 3),
|
||||
(&BasePassSlots::ScreenSize, 4),
|
||||
],
|
||||
);
|
||||
|
||||
pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1);
|
||||
}
|
||||
}
|