├── .gitignore
├── CONTRIBUTING.md
├── Cargo.toml
├── LICENSE
├── README.md
├── saver_bevymin
├── Cargo.toml
└── src
│ └── main.rs
├── saver_colorstatic
├── .gitignore
├── Cargo.toml
└── src
│ └── main.rs
├── saver_genetic_orbits
├── .gitignore
├── Cargo.toml
├── assets
│ ├── fonts
│ │ ├── FiraMono-Regular.ttf
│ │ ├── FiraSans-Book.ttf
│ │ └── LICENSE-FIRA
│ └── skyboxes
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ └── 4.png
├── build.rs
└── src
│ ├── config
│ ├── camera.rs
│ ├── database.rs
│ ├── generator.rs
│ ├── mod.rs
│ ├── scoring.rs
│ └── util.rs
│ ├── main.rs
│ ├── model.rs
│ ├── skyboxes.rs
│ ├── statustracker
│ ├── mod.rs
│ └── scoring_function
│ │ ├── expression_serde.rs
│ │ ├── mod.rs
│ │ ├── scoring_function_parser.lalrpop
│ │ └── transforms.rs
│ ├── storage
│ ├── mod.rs
│ ├── pruner.rs
│ └── sqlite.rs
│ ├── world.rs
│ └── worldgenerator.rs
├── saver_sfmlrect
├── .gitignore
├── Cargo.toml
└── src
│ └── main.rs
├── sigint
├── .gitignore
├── Cargo.toml
└── src
│ └── lib.rs
├── third_party
└── bevy_wgpu_xsecurelock
│ ├── Cargo.toml
│ ├── LICENSE
│ ├── LICENSE-APACHE
│ ├── LICENSE-MIT
│ └── src
│ ├── diagnostic
│ ├── mod.rs
│ └── wgpu_resource_diagnostics_plugin.rs
│ ├── lib.rs
│ ├── renderer
│ ├── mod.rs
│ ├── wgpu_render_context.rs
│ ├── wgpu_render_graph_executor.rs
│ └── wgpu_render_resource_context.rs
│ ├── wgpu_render_pass.rs
│ ├── wgpu_renderer.rs
│ ├── wgpu_resources.rs
│ └── wgpu_type_converter.rs
└── xsecurelock-saver
├── Cargo.toml
└── src
├── engine.rs
├── lib.rs
└── simple.rs
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/rust
3 |
4 | ### Rust ###
5 | # Generated by Cargo
6 | # will have compiled files and executables
7 | /target/
8 |
9 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
10 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
11 | /Cargo.lock
12 |
13 | # These are backup files generated by rustfmt
14 | **/*.rs.bk
15 |
16 |
17 | # End of https://www.gitignore.io/api/rust
18 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution;
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
18 | ## Code reviews
19 |
20 | All submissions, including submissions by project members, require review. We
21 | use GitHub pull requests for this purpose. Consult
22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
23 | information on using pull requests.
24 |
25 | ## Community Guidelines
26 |
27 | This project follows
28 | [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/).
29 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "third_party/bevy_wgpu_xsecurelock",
4 | "saver_bevymin",
5 | "saver_colorstatic",
6 | "saver_genetic_orbits",
7 | "saver_sfmlrect",
8 | "sigint",
9 | "xsecurelock-saver",
10 | ]
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # XSecureLock Saver
2 |
3 | A library for creating 2D screensavers for [XSecureLock][xsecurelock] in Rust,
4 | using either [SFML][sfml] or [Bevy][bevy].
5 |
6 | [xsecurelock]: https://github.com/google/xsecurelock
7 | [sfml]: https://www.sfml-dev.org/
8 | [bevy]: https://bevyengine.org/
9 |
10 | # License
11 |
12 | The code is released unser the Apache 2.0 license. See the LICENSE file for more
13 | details.
14 |
15 | This project is not an official Google project. It is not supported by Google
16 | and Google specifically disclaims all warranties as to its quality,
17 | merchantability, or fitness for a particular purpose.
18 |
--------------------------------------------------------------------------------
/saver_bevymin/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "saver_bevymin"
3 | version = "0.1.0"
4 | edition = "2018"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | bevy = "0.5.0"
10 | bevy_skybox_cubemap = "0.1.0"
11 | xsecurelock-saver = { path = "../xsecurelock-saver", features = ["engine"] }
12 |
--------------------------------------------------------------------------------
/saver_bevymin/src/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | use bevy::prelude::*;
15 | use bevy::render::camera::{Camera, PerspectiveProjection};
16 | use bevy_skybox_cubemap::{SkyboxBundle, SkyboxMaterial, SkyboxPlugin};
17 | use xsecurelock_saver::engine::XSecurelockSaverPlugins;
18 |
19 | fn main() {
20 | App::build()
21 | .insert_resource(ClearColor(Color::rgb(0.5, 0.5, 0.9)))
22 | .insert_resource(Msaa { samples: 4 })
23 | .add_plugins(XSecurelockSaverPlugins)
24 | .add_plugin(SkyboxPlugin)
25 | .add_startup_system(setup.system())
26 | .add_system(spin_camera.system())
27 | .run();
28 | }
29 |
30 | fn spin_camera(
31 | time: Res,
32 | mut query: Query<&mut Transform, (With, With)>,
33 | ) {
34 | const SPEED: f32 = 0.5;
35 | const DIST: f32 = 6.0;
36 | let t = time.seconds_since_startup() as f32;
37 | for mut trans in query.iter_mut() {
38 | let (sin, cos) = (t * SPEED).sin_cos();
39 | *trans = Transform::from_xyz(sin * DIST, 2.5, cos * DIST).looking_at(Vec3::ZERO, Vec3::Y);
40 | }
41 | }
42 |
43 | /// set up a simple 3D scene
44 | fn setup(
45 | mut commands: Commands,
46 | mut meshes: ResMut>,
47 | mut materials: ResMut>,
48 | mut skyboxes: ResMut>,
49 | ) {
50 | // background color
51 | commands.spawn_bundle(SkyboxBundle::new(skyboxes.add(SkyboxMaterial {
52 | color: Color::rgb(0.1, 0.1, 0.7),
53 | ..Default::default()
54 | })));
55 | // plane
56 | commands.spawn_bundle(PbrBundle {
57 | mesh: meshes.add(Mesh::from(shape::Plane { size: 50.0 })),
58 | material: materials.add(Color::rgb(0.3, 0.5, 0.3).into()),
59 | ..Default::default()
60 | });
61 | // cube
62 | commands.spawn_bundle(PbrBundle {
63 | mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
64 | material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()),
65 | transform: Transform::from_xyz(0.0, 0.5, 0.0),
66 | ..Default::default()
67 | });
68 | // light
69 | commands.spawn_bundle(LightBundle {
70 | transform: Transform::from_xyz(4.0, 8.0, 4.0),
71 | ..Default::default()
72 | });
73 | // camera
74 | commands.spawn_bundle(PerspectiveCameraBundle {
75 | transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
76 | ..Default::default()
77 | });
78 | }
79 |
--------------------------------------------------------------------------------
/saver_colorstatic/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/rust
3 |
4 | ### Rust ###
5 | # Generated by Cargo
6 | # will have compiled files and executables
7 | /target/
8 |
9 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
10 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
11 | # Cargo.lock
12 |
13 | # These are backup files generated by rustfmt
14 | **/*.rs.bk
15 |
16 |
17 | # End of https://www.gitignore.io/api/rust
18 |
--------------------------------------------------------------------------------
/saver_colorstatic/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "saver_colorstatic"
3 | version = "0.1.0"
4 | authors = ["Zachary Stewart "]
5 |
6 | [dependencies]
7 | rand = "0.5"
8 | sfml = "0.16"
9 | xsecurelock-saver = { path = "../xsecurelock-saver", features = ["simple"] }
10 |
--------------------------------------------------------------------------------
/saver_colorstatic/src/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | extern crate rand;
16 | extern crate sfml;
17 | extern crate xsecurelock_saver;
18 |
19 | use sfml::graphics::{Color, Image, RenderTarget, Sprite, Texture};
20 | use sfml::system::Vector2u;
21 |
22 | use xsecurelock_saver::simple::Screensaver;
23 |
24 | struct StaticScreensaver {
25 | img: Image,
26 | }
27 |
28 | impl Screensaver for StaticScreensaver {
29 | fn update(&mut self) {
30 | for Vector2u { x, y } in row_major_iterator(self.img.size()) {
31 | self.img.set_pixel(
32 | x,
33 | y,
34 | Color::rgb(rand::random(), rand::random(), rand::random()),
35 | );
36 | }
37 | }
38 |
39 | fn draw(&self, target: &mut T) {
40 | let tex = Texture::from_image(&self.img).unwrap();
41 | let sprite = Sprite::with_texture(&tex);
42 |
43 | target.draw(&sprite);
44 | }
45 | }
46 |
47 | fn main() {
48 | xsecurelock_saver::simple::run_saver(|screen_size| StaticScreensaver {
49 | img: Image::new(screen_size.x, screen_size.y),
50 | });
51 | }
52 |
53 | fn row_major_iterator(size: V) -> impl Iterator-
54 | where
55 | V: Into
,
56 | {
57 | let Vector2u {
58 | x: width,
59 | y: height,
60 | } = size.into();
61 | (0..height).flat_map(move |y| (0..width).map(move |x| Vector2u { x: x, y: y }))
62 | }
63 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/rust
3 |
4 | ### Rust ###
5 | # Generated by Cargo
6 | # will have compiled files and executables
7 | /target/
8 |
9 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
10 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
11 | # Cargo.lock
12 |
13 | # These are backup files generated by rustfmt
14 | **/*.rs.bk
15 |
16 |
17 | # End of https://www.gitignore.io/api/rust
18 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "saver_genetic_orbits"
3 | version = "0.1.0"
4 | authors = ["Zachary Stewart "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | clap = "2"
9 | bevy = { version = "0.5.0", features = ["serialize"] }
10 | bevy_rapier3d = "0.11.0"
11 | bevy_skybox_cubemap = "0.1.0"
12 | dirs = "4"
13 | figment = { version = "0.10" , features = ["yaml"] }
14 | humantime-serde = "1"
15 | lalrpop-util = "0.19"
16 | log = "0.4"
17 | rand = "0.8"
18 | rand_distr = "0.4"
19 | regex = "1.0"
20 | rusqlite = "0.15"
21 | serde = "1"
22 | serde_json = "1"
23 | xsecurelock-saver = { path = "../xsecurelock-saver", features = ["engine"] }
24 |
25 | [build-dependencies]
26 | lalrpop = "0.19"
27 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/assets/fonts/FiraMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/xsecurelock-saver-rs/eb21e2e32977cd46ff7e229244a80a4511ba3b22/saver_genetic_orbits/assets/fonts/FiraMono-Regular.ttf
--------------------------------------------------------------------------------
/saver_genetic_orbits/assets/fonts/FiraSans-Book.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/xsecurelock-saver-rs/eb21e2e32977cd46ff7e229244a80a4511ba3b22/saver_genetic_orbits/assets/fonts/FiraSans-Book.ttf
--------------------------------------------------------------------------------
/saver_genetic_orbits/assets/fonts/LICENSE-FIRA:
--------------------------------------------------------------------------------
1 | Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A.
2 |
3 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
4 | This license is copied below, and is also available with a FAQ at:
5 | http://scripts.sil.org/OFL
6 |
7 |
8 | -----------------------------------------------------------
9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
10 | -----------------------------------------------------------
11 |
12 | PREAMBLE
13 | The goals of the Open Font License (OFL) are to stimulate worldwide
14 | development of collaborative font projects, to support the font creation
15 | efforts of academic and linguistic communities, and to provide a free and
16 | open framework in which fonts may be shared and improved in partnership
17 | with others.
18 |
19 | The OFL allows the licensed fonts to be used, studied, modified and
20 | redistributed freely as long as they are not sold by themselves. The
21 | fonts, including any derivative works, can be bundled, embedded,
22 | redistributed and/or sold with any software provided that any reserved
23 | names are not used by derivative works. The fonts and derivatives,
24 | however, cannot be released under any other type of license. The
25 | requirement for fonts to remain under this license does not apply
26 | to any document created using the fonts or their derivatives.
27 |
28 | DEFINITIONS
29 | "Font Software" refers to the set of files released by the Copyright
30 | Holder(s) under this license and clearly marked as such. This may
31 | include source files, build scripts and documentation.
32 |
33 | "Reserved Font Name" refers to any names specified as such after the
34 | copyright statement(s).
35 |
36 | "Original Version" refers to the collection of Font Software components as
37 | distributed by the Copyright Holder(s).
38 |
39 | "Modified Version" refers to any derivative made by adding to, deleting,
40 | or substituting -- in part or in whole -- any of the components of the
41 | Original Version, by changing formats or by porting the Font Software to a
42 | new environment.
43 |
44 | "Author" refers to any designer, engineer, programmer, technical
45 | writer or other person who contributed to the Font Software.
46 |
47 | PERMISSION & CONDITIONS
48 | Permission is hereby granted, free of charge, to any person obtaining
49 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
50 | redistribute, and sell modified and unmodified copies of the Font
51 | Software, subject to the following conditions:
52 |
53 | 1) Neither the Font Software nor any of its individual components,
54 | in Original or Modified Versions, may be sold by itself.
55 |
56 | 2) Original or Modified Versions of the Font Software may be bundled,
57 | redistributed and/or sold with any software, provided that each copy
58 | contains the above copyright notice and this license. These can be
59 | included either as stand-alone text files, human-readable headers or
60 | in the appropriate machine-readable metadata fields within text or
61 | binary files as long as those fields can be easily viewed by the user.
62 |
63 | 3) No Modified Version of the Font Software may use the Reserved Font
64 | Name(s) unless explicit written permission is granted by the corresponding
65 | Copyright Holder. This restriction only applies to the primary font name as
66 | presented to the users.
67 |
68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
69 | Software shall not be used to promote, endorse or advertise any
70 | Modified Version, except to acknowledge the contribution(s) of the
71 | Copyright Holder(s) and the Author(s) or with their explicit written
72 | permission.
73 |
74 | 5) The Font Software, modified or unmodified, in part or in whole,
75 | must be distributed entirely under this license, and must not be
76 | distributed under any other license. The requirement for fonts to
77 | remain under this license does not apply to any document created
78 | using the Font Software.
79 |
80 | TERMINATION
81 | This license becomes null and void if any of the above conditions are
82 | not met.
83 |
84 | DISCLAIMER
85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
93 | OTHER DEALINGS IN THE FONT SOFTWARE.
94 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/assets/skyboxes/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/xsecurelock-saver-rs/eb21e2e32977cd46ff7e229244a80a4511ba3b22/saver_genetic_orbits/assets/skyboxes/1.png
--------------------------------------------------------------------------------
/saver_genetic_orbits/assets/skyboxes/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/xsecurelock-saver-rs/eb21e2e32977cd46ff7e229244a80a4511ba3b22/saver_genetic_orbits/assets/skyboxes/2.png
--------------------------------------------------------------------------------
/saver_genetic_orbits/assets/skyboxes/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/xsecurelock-saver-rs/eb21e2e32977cd46ff7e229244a80a4511ba3b22/saver_genetic_orbits/assets/skyboxes/3.png
--------------------------------------------------------------------------------
/saver_genetic_orbits/assets/skyboxes/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/google/xsecurelock-saver-rs/eb21e2e32977cd46ff7e229244a80a4511ba3b22/saver_genetic_orbits/assets/skyboxes/4.png
--------------------------------------------------------------------------------
/saver_genetic_orbits/build.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | extern crate lalrpop;
16 |
17 | fn main() {
18 | lalrpop::process_root().unwrap();
19 | }
20 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/config/camera.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use serde::{Deserialize, Serialize};
16 |
17 | /// Configuration for the scenario camera.
18 | #[derive(Serialize, Deserialize, Debug, Clone)]
19 | #[serde(default)]
20 | pub struct CameraConfig {
21 | /// Relative rotation speed.
22 | pub rotation_speed: f32,
23 |
24 | /// How far from the origin the camera should be.
25 | pub view_dist: f32,
26 | }
27 |
28 | impl Default for CameraConfig {
29 | fn default() -> Self {
30 | Self {
31 | rotation_speed: 0.1,
32 | view_dist: 1000.0,
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/config/database.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Contains configuration structs for the database.
16 |
17 | use std::path::PathBuf;
18 |
19 | use serde::{Deserialize, Serialize};
20 |
21 | /// Configuration parameters for the Sqlite Database.
22 | #[derive(Serialize, Deserialize, Debug, Clone)]
23 | #[serde(default)]
24 | pub struct DatabaseConfig {
25 | /// The path to the SqliteDatabase to use. If set, the parent directory must exist and the
26 | /// location must be writable. Saver will never fall back to an in-memory database if this is
27 | /// set.
28 | #[serde(skip_serializing_if = "Option::is_none")]
29 | pub database_path: Option,
30 |
31 | /// Sets the cap for the number of scenarios to keep in the database. Set to None for
32 | /// unlimited. Defaults to 1,000,000.
33 | #[serde(skip_serializing_if = "Option::is_none")]
34 | pub max_scenarios_to_keep: Option,
35 |
36 | /// How often (in seconds) to prune excess scenarios while running normally. Defaults to every
37 | /// 20 minutes (1200 seconds). Regardless of what this is set to, it will always prune on
38 | /// shutdown unless max_scenarios_to_keep is unset.
39 | pub prune_interval_seconds: u64,
40 | }
41 |
42 | impl Default for DatabaseConfig {
43 | fn default() -> Self {
44 | DatabaseConfig {
45 | database_path: None,
46 | max_scenarios_to_keep: Some(1000000),
47 | prune_interval_seconds: 1200,
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/config/generator.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Contains configuration structs for the world generator.
16 |
17 | use serde::de::{Error, Unexpected};
18 | use serde::{Deserialize, Deserializer, Serialize};
19 |
20 | use crate::config::util::{
21 | Distribution, ExponentialDistribution, NormalDistribution, Range, UniformDistribution,
22 | Vector as SerVec,
23 | };
24 |
25 | /// Tuning parameters for the world generator/mutator.
26 | #[derive(Serialize, Deserialize, Debug, Clone)]
27 | #[serde(default)]
28 | pub struct GeneratorConfig {
29 | /// The probability of generating a new scenario. Parent scenarios are chosen with an
30 | /// exponential distribution over all current scenarios. The lambda for the exponential
31 | /// distribution is chosen so that there is `create_new_scenario_probability` of getting an
32 | /// index outside of the existing scenario range, which triggers generating a new scenario.
33 | #[serde(deserialize_with = "deserialize_percent")]
34 | pub create_new_scenario_probability: f64,
35 |
36 | /// The parameters affecting world mutation.
37 | pub mutation_parameters: MutationParameters,
38 |
39 | /// The parameters affecting new world generation.
40 | pub new_world_parameters: NewWorldParameters,
41 | }
42 |
43 | /// Deserializes the a float, erroring if it isn't in range [0,1].
44 | fn deserialize_percent<'de, D>(deserializer: D) -> Result
45 | where
46 | D: Deserializer<'de>,
47 | {
48 | let val = f64::deserialize(deserializer)?;
49 | if val < 0.0 || val > 1.0 {
50 | Err(D::Error::invalid_value(
51 | Unexpected::Float(val),
52 | &"a float between 0 and 1 inclusive",
53 | ))
54 | } else {
55 | Ok(val)
56 | }
57 | }
58 |
59 | impl Default for GeneratorConfig {
60 | fn default() -> Self {
61 | GeneratorConfig {
62 | create_new_scenario_probability: 0.05,
63 | mutation_parameters: Default::default(),
64 | new_world_parameters: Default::default(),
65 | }
66 | }
67 | }
68 |
69 | /// Parameters that control initial world generation.
70 | #[derive(Serialize, Deserialize, Debug, Clone)]
71 | #[serde(default)]
72 | pub struct MutationParameters {
73 | /// The min and max number of planets to add. Used as a clamp on the add_planets_distribution.
74 | /// Defaults to [0, 20]. Max is inclusive.
75 | #[serde(deserialize_with = "Range::deserialize_reorder")]
76 | pub add_planets_limits: Range,
77 |
78 | /// Distribution over the number of new planets to add. If using a uniform distribution, the
79 | /// range is inclusive. Exponential distribution rounds down, normal distribution rounds to
80 | /// nearest.
81 | /// The default value is an exponential distribution with lambda chosen to have a 99.9% chance
82 | /// of having fewer than 10 new planets.
83 | pub add_planets_dist: Distribution,
84 |
85 | /// The parameters affecting new planets that get added in this mutation.
86 | pub new_planet_parameters: NewPlanetParameters,
87 |
88 | /// The min and max number of planets to remove. Used as a clamp on the
89 | /// remove_planets_distribution. Defaults to [0, 20]. Max is inclusive.
90 | #[serde(deserialize_with = "Range::deserialize_reorder")]
91 | pub remove_planets_limits: Range,
92 |
93 | /// Distribution over the number of new planets to remove. If using a uniform distribution, the
94 | /// range is inclusive. Exponential distribution rounds down, normal distribution rounds to
95 | /// nearest.
96 | /// The default value is an exponential distribution with lambda chosen to have a 99.9% chance
97 | /// of removing fewer than 10 planets.
98 | pub remove_planets_dist: Distribution,
99 |
100 | /// Percentage of planets to change, on average.
101 | #[serde(deserialize_with = "deserialize_percent")]
102 | pub fraction_of_planets_to_change: f64,
103 |
104 | /// Parameters for how to mutate individual planets.
105 | pub planet_mutation_parameters: PlanetMutationParameters,
106 | }
107 |
108 | impl Default for MutationParameters {
109 | fn default() -> Self {
110 | const DEFAULT_ADD_REMOVE_PLANETS_LIMITS: Range = Range { min: 0, max: 20 };
111 | // -ln(1 - .999) / 10 = 99.9% chance of adding or removing fewer than 10 planets.
112 | const DEFAULT_ADD_REMOVE_PLANETS_DIST: Distribution =
113 | Distribution::Exponential(ExponentialDistribution(0.6907755278982136));
114 | MutationParameters {
115 | add_planets_limits: DEFAULT_ADD_REMOVE_PLANETS_LIMITS,
116 | add_planets_dist: DEFAULT_ADD_REMOVE_PLANETS_DIST,
117 | new_planet_parameters: Default::default(),
118 | remove_planets_limits: DEFAULT_ADD_REMOVE_PLANETS_LIMITS,
119 | remove_planets_dist: DEFAULT_ADD_REMOVE_PLANETS_DIST,
120 | fraction_of_planets_to_change: 0.10,
121 | planet_mutation_parameters: Default::default(),
122 | }
123 | }
124 | }
125 |
126 | #[derive(Serialize, Deserialize, Debug, Clone)]
127 | #[serde(default)]
128 | pub struct NewWorldParameters {
129 | /// Inclusive range over the number of planets that should be generated. Used to cap
130 | /// distributions with long tails. Defaults to [1, 1000].
131 | #[serde(deserialize_with = "Range::deserialize_reorder")]
132 | pub num_planets_range: Range,
133 | /// Distribution used for selecting the number of planets to distribute over. If using a
134 | /// uniform distribution, the range is inclusive. Exponential distribution rounds down, normal
135 | /// distribution rounds to nearest.
136 | /// The default value is an exponential distribution with lambda chosen to have a 99.999%
137 | /// chance of picking fewer than 1000 planets.
138 | pub num_planets_dist: Distribution,
139 | /// Parameters for how new planets are generated.
140 | pub planet_parameters: NewPlanetParameters,
141 | }
142 |
143 | impl Default for NewWorldParameters {
144 | fn default() -> Self {
145 | NewWorldParameters {
146 | num_planets_range: Range { min: 1, max: 1000 },
147 | num_planets_dist:
148 | // -ln(1 - .99999) / 1000 = 99.999% chance of choosing fewer than 1000 planets.
149 | Distribution::Exponential(ExponentialDistribution(0.01151292546497023)),
150 | planet_parameters: Default::default(),
151 | }
152 | }
153 | }
154 |
155 | /// Parameters to control how new planets are generated.
156 | #[derive(Serialize, Deserialize, Debug, Clone)]
157 | #[serde(default)]
158 | pub struct NewPlanetParameters {
159 | /// The distribution the generated planet's start position. Defaults to [-2000, 2000] in each
160 | /// axis.
161 | pub start_position: SerVec,
162 | /// Controls the distribution of starting velocities for planets. Defaults to mean: 0,
163 | /// stddev:
164 | /// 20 in both x and y.
165 | pub start_velocity: SerVec,
166 | /// A minimum limit on the starting mass of planets. Should be positve (i.e. greater than
167 | /// zero). defaults to 1.
168 | #[serde(deserialize_with = "deserialize_min_mass")]
169 | pub min_start_mass: f32,
170 | /// Controls the distribution of starting masses for planets. Defaults to mean: 500.
171 | /// stddev: 400.
172 | pub start_mass: NormalDistribution,
173 | }
174 |
175 | impl Default for NewPlanetParameters {
176 | fn default() -> Self {
177 | NewPlanetParameters {
178 | start_position: SerVec {
179 | x: UniformDistribution {
180 | min: -500.0,
181 | max: 500.0,
182 | },
183 | y: UniformDistribution {
184 | min: -500.0,
185 | max: 500.0,
186 | },
187 | z: UniformDistribution {
188 | min: -500.0,
189 | max: 500.0,
190 | },
191 | },
192 | start_velocity: SerVec {
193 | x: NormalDistribution {
194 | mean: 0.,
195 | standard_deviation: 20.,
196 | },
197 | y: NormalDistribution {
198 | mean: 0.,
199 | standard_deviation: 20.,
200 | },
201 | z: NormalDistribution {
202 | mean: 0.,
203 | standard_deviation: 20.,
204 | },
205 | },
206 | min_start_mass: 1.,
207 | start_mass: NormalDistribution {
208 | mean: 500.,
209 | standard_deviation: 400.,
210 | },
211 | }
212 | }
213 | }
214 |
215 | /// Deserializes the min mass, erroring if not positive.
216 | fn deserialize_min_mass<'de, D>(deserializer: D) -> Result
217 | where
218 | D: Deserializer<'de>,
219 | {
220 | let val = f32::deserialize(deserializer)?;
221 | if val <= 0.0 {
222 | Err(D::Error::invalid_value(
223 | Unexpected::Float(val as f64),
224 | &"a positive float",
225 | ))
226 | } else {
227 | Ok(val)
228 | }
229 | }
230 |
231 | /// Parameters to control how planets are mutated.
232 | #[derive(Serialize, Deserialize, Debug, Clone)]
233 | #[serde(default)]
234 | pub struct PlanetMutationParameters {
235 | /// Distribution for how much to change position when modifying the planet. Defaults to a mean
236 | /// of 0 and a standard deviation of 10 in both x and y.
237 | pub position_change: SerVec,
238 |
239 | /// Distribution for how much to change velocity when modifying the planet. Defaults to a mean
240 | /// of 0 and a standard deviation of 10 in both x and y.
241 | pub velocity_change: SerVec,
242 |
243 | /// Distribution for how much to change mass when modifying the planet. Defaults to a normal
244 | /// distribution with a mean of 0 and a standard deviation of 100. Cannot be an exponential
245 | /// distribution because those only go up.
246 | #[serde(deserialize_with = "deserialize_mass_change")]
247 | pub mass_change: Distribution,
248 |
249 | /// Min mass that the planet must have, used to clamp the results of the mass change must be
250 | /// positive. Default is 1.
251 | #[serde(deserialize_with = "deserialize_min_mass")]
252 | pub min_mass: f32,
253 | }
254 |
255 | impl Default for PlanetMutationParameters {
256 | fn default() -> Self {
257 | const DEFAULT_VEC_CHANGE: SerVec = SerVec {
258 | x: NormalDistribution {
259 | mean: 0.,
260 | standard_deviation: 10.,
261 | },
262 | y: NormalDistribution {
263 | mean: 0.,
264 | standard_deviation: 10.,
265 | },
266 | z: NormalDistribution {
267 | mean: 0.,
268 | standard_deviation: 10.,
269 | },
270 | };
271 | PlanetMutationParameters {
272 | position_change: DEFAULT_VEC_CHANGE,
273 | velocity_change: DEFAULT_VEC_CHANGE,
274 | mass_change: Distribution::Normal(NormalDistribution {
275 | mean: 0.,
276 | standard_deviation: 100.,
277 | }),
278 | min_mass: 1.,
279 | }
280 | }
281 | }
282 |
283 | /// Deserializes the min mass, erroring if not positive.
284 | fn deserialize_mass_change<'de, D>(deserializer: D) -> Result
285 | where
286 | D: Deserializer<'de>,
287 | {
288 | let val = Distribution::deserialize(deserializer)?;
289 | if let Distribution::Exponential(_) = val {
290 | Err(D::Error::invalid_value(
291 | Unexpected::TupleVariant,
292 | &"a non-exponential distribution",
293 | ))
294 | } else {
295 | Ok(val)
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/config/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Contains structs used for configuring the screensaver.
16 |
17 | use bevy::prelude::*;
18 | use figment::providers::{Format, Serialized, Yaml};
19 | use figment::Figment;
20 |
21 | use self::camera::CameraConfig;
22 | use self::database::DatabaseConfig;
23 | use self::generator::GeneratorConfig;
24 | use self::scoring::ScoringConfig;
25 |
26 | pub mod camera;
27 | pub mod database;
28 | pub mod generator;
29 | pub mod scoring;
30 | pub mod util;
31 |
32 | /// The screensaver folder name, used both for saving the database in the user data directory and
33 | /// for looking for configs in the
34 | const SAVER_DIR: &'static str = "xsecurelock-saver-genetic-orbits";
35 |
36 | /// Adds figment-based configs.
37 | pub struct ConfigPlugin;
38 |
39 | impl Plugin for ConfigPlugin {
40 | fn build(&self, app: &mut AppBuilder) {
41 | let mut figment = Figment::new();
42 |
43 | if let Some(mut data_dir) = dirs::data_dir() {
44 | data_dir.push(SAVER_DIR);
45 | data_dir.push("scenario-db.sqlite3");
46 | figment = figment.merge(Serialized::defaults(DatabaseConfig {
47 | database_path: Some(data_dir),
48 | ..Default::default()
49 | }));
50 | }
51 |
52 | if let Some(mut config_dir) = dirs::config_dir() {
53 | config_dir.push(SAVER_DIR);
54 | config_dir.push("config.yaml");
55 | figment = figment.merge(Yaml::file(config_dir));
56 | }
57 |
58 | if let Some(mut home_dir) = dirs::home_dir() {
59 | home_dir.push(".xsecurelock-saver-genetic-orbits.yaml");
60 | figment = figment.merge(Yaml::file(home_dir));
61 | }
62 |
63 | let camconf = figment.extract::().unwrap();
64 | let dbconf = figment.extract::().unwrap();
65 | let scoreconf = figment.extract::().unwrap();
66 | let genconf = figment.extract::().unwrap();
67 |
68 | info!("Loaded camera config: {:?}", camconf);
69 | info!("Loaded database config: {:?}", dbconf);
70 | info!("Loaded score config: {:?}", scoreconf);
71 | info!("Loaded generator config: {:?}", genconf);
72 |
73 | app.insert_resource(camconf)
74 | .insert_resource(dbconf)
75 | .insert_resource(scoreconf)
76 | .insert_resource(genconf);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/config/scoring.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Contains configuration structs for the scoring system.
16 |
17 | use std::time::Duration;
18 |
19 | use serde::de::{Error, Unexpected};
20 | use serde::{Deserialize, Deserializer, Serialize};
21 |
22 | use crate::statustracker::ScoringFunction;
23 |
24 | /// Tuning parameters for world scoring.
25 | #[derive(Serialize, Deserialize, Debug, Clone)]
26 | #[serde(default)]
27 | pub struct ScoringConfig {
28 | /// The number of physics ticks to count the score for. Physics ticks are defined to be 16
29 | /// milliseconds long. Defaults to 3750, which is approximately 60 seconds.
30 | #[serde(with = "humantime_serde")]
31 | pub scored_time: Duration,
32 |
33 | /// The region where planets actually count towards the scenario score.
34 | pub scored_area: ScoredArea,
35 |
36 | /// Expression that is evaluated each frame to determine the score for that frame, to be added
37 | /// to the cumulative score. This is a simple math expression and can use three variables:
38 | ///
39 | /// - `elapsed` is the percentage of scenario time that has completed, from 0 to 1.
40 | /// - `total_mass` is the total mass of all planets in the `scored_area`.
41 | /// - `mass_count` is the number of masses in the `scored_area`.
42 | ///
43 | /// The score is "per second" because the output is multiplied by delta time before adding it to
44 | /// the total score.
45 | pub score_per_second: ScoringFunction,
46 | }
47 |
48 | impl Default for ScoringConfig {
49 | fn default() -> Self {
50 | ScoringConfig {
51 | scored_time: Duration::from_secs(60),
52 | scored_area: Default::default(),
53 | score_per_second: "total_mass * mass_count".parse().unwrap(),
54 | }
55 | }
56 | }
57 |
58 | /// Defines the area where planets are actually scored. Area is centered on the origin, and planets
59 | /// outside of it don't get any score.
60 | #[derive(Serialize, Deserialize, Debug, Clone)]
61 | #[serde(default)]
62 | pub struct ScoredArea {
63 | // TODO(zstewar1): use a Range for the scored area.
64 | /// The width (x) of the scored region. Defaults to 4000.
65 | #[serde(deserialize_with = "scored_area_whd_deserialize")]
66 | pub width: f32,
67 | /// The height (y) of the scored region. Defaults to 4000.
68 | #[serde(deserialize_with = "scored_area_whd_deserialize")]
69 | pub height: f32,
70 | /// The depth (z) of the scored region. Defaults to 4000.
71 | #[serde(deserialize_with = "scored_area_whd_deserialize")]
72 | pub depth: f32,
73 | }
74 |
75 | impl Default for ScoredArea {
76 | fn default() -> Self {
77 | ScoredArea {
78 | width: 4000.0,
79 | height: 4000.0,
80 | depth: 4000.0,
81 | }
82 | }
83 | }
84 |
85 | /// Deserializes the width or height of ScoredArea, flipping negatives and changing 0 to 4000.
86 | fn scored_area_whd_deserialize<'de, D>(deserializer: D) -> Result
87 | where
88 | D: Deserializer<'de>,
89 | {
90 | let val = f32::deserialize(deserializer)?;
91 | if val <= 0.0 {
92 | Err(D::Error::invalid_value(
93 | Unexpected::Float(val as f64),
94 | &"a float > 0",
95 | ))
96 | } else {
97 | Ok(val)
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/config/util.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Contains serializable utility structs which are useful for other config structs.
16 |
17 | use serde::de::{Error, Unexpected};
18 | use serde::{Deserialize, Deserializer, Serialize};
19 |
20 | /// Fully serializable generic vector.
21 | #[derive(Serialize, Deserialize, Debug, Clone)]
22 | pub struct Vector {
23 | pub x: T,
24 | pub y: T,
25 | pub z: T,
26 | }
27 |
28 | /// A range over a generic group of elements. May be inclusive or exclusive depending on context.
29 | /// Both parameters must be specified when this is specified explicitly.
30 | #[derive(Serialize, Deserialize, Debug, Clone)]
31 | pub struct Range {
32 | pub min: T,
33 | pub max: T,
34 | }
35 |
36 | impl<'de, T> Range
37 | where
38 | T: PartialOrd + Deserialize<'de>,
39 | {
40 | pub(super) fn deserialize_reorder(deserializer: D) -> Result
41 | where
42 | D: Deserializer<'de>,
43 | {
44 | let mut res = Self::deserialize(deserializer)?;
45 | if res.min > res.max {
46 | std::mem::swap(&mut res.min, &mut res.max);
47 | }
48 | Ok(res)
49 | }
50 | }
51 |
52 | impl Range
53 | where
54 | T: PartialOrd + Clone,
55 | {
56 | /// Clamps the value to be within this range, assuming the range is inclusive.
57 | pub fn clamp_inclusive(&self, val: T) -> T {
58 | assert!(self.min <= self.max, "min must be less than max");
59 | if val < self.min {
60 | self.min.clone()
61 | } else if val > self.max {
62 | self.max.clone()
63 | } else {
64 | val
65 | }
66 | }
67 | }
68 |
69 | /// A random distribution. This enum is used in places where the configuration should have a choice
70 | /// of several different distribution types.
71 | #[derive(Serialize, Deserialize, Debug, Clone)]
72 | #[serde(tag = "type")]
73 | #[serde(rename_all = "snake_case")]
74 | pub enum Distribution {
75 | /// Use an exponential distribution.
76 | Exponential(ExponentialDistribution),
77 | /// Use a normal distribution.
78 | Normal(NormalDistribution),
79 | /// Use a uniform distribution.
80 | Uniform(UniformDistribution),
81 | }
82 |
83 | /// A distribution that is required to be exponential. Serializable rand::distributions::Exp.
84 | #[derive(Serialize, Deserialize, Debug, Clone)]
85 | pub struct ExponentialDistribution(
86 | #[serde(deserialize_with = "deserialize_exponential_lambda")] pub f64,
87 | );
88 |
89 | /// Deserializes an exponential distribution, flipping negative values and setting 0 to 1.
90 | fn deserialize_exponential_lambda<'de, D>(deserializer: D) -> Result
91 | where
92 | D: Deserializer<'de>,
93 | {
94 | let val = f64::deserialize(deserializer)?;
95 | if val <= 0.0 {
96 | Err(D::Error::invalid_value(
97 | Unexpected::Float(val),
98 | &"a float > 0",
99 | ))
100 | } else {
101 | Ok(val)
102 | }
103 | }
104 |
105 | /// A distribution that is required to be normal. Serializable rand::distributions::Normal.
106 | #[derive(Serialize, Deserialize, Debug, Clone)]
107 | pub struct NormalDistribution {
108 | /// The mean of the normal distribution.
109 | pub mean: f64,
110 | /// The standard deviation of the normal distribution.
111 | #[serde(deserialize_with = "deserialize_normal_mean")]
112 | pub standard_deviation: f64,
113 | }
114 |
115 | /// Deserializes an exponential distribution, flipping negative values.
116 | fn deserialize_normal_mean<'de, D>(deserializer: D) -> Result
117 | where
118 | D: Deserializer<'de>,
119 | {
120 | Ok(f64::deserialize(deserializer)?.abs())
121 | }
122 |
123 | /// A distribution that is required to be uniform. Serializable rand::distributions::Uniform.
124 | #[derive(Serialize, Deserialize, Debug, Clone)]
125 | #[serde(try_from = "uniform_distribution_de::UniformDistribution")]
126 | pub struct UniformDistribution {
127 | /// The min value for the uniform distribution (always inclusive).
128 | pub min: f64,
129 | /// The max value for the uniform distribution (may be inclusive or exclusive depending on
130 | /// context).
131 | pub max: f64,
132 | }
133 |
134 | mod uniform_distribution_de {
135 | use serde::Deserialize;
136 |
137 | /// Shadow type that can implement Deserialize.
138 | #[derive(Deserialize, Debug, Clone)]
139 | pub(super) struct UniformDistribution {
140 | /// The min value for the uniform distribution (always inclusive).
141 | min: f64,
142 | /// The max value for the uniform distribution (may be inclusive or exclusive depending on
143 | /// context).
144 | max: f64,
145 | }
146 |
147 | impl From for super::UniformDistribution {
148 | fn from(mut ud: UniformDistribution) -> Self {
149 | if ud.min > ud.max {
150 | std::mem::swap(&mut ud.min, &mut ud.max);
151 | }
152 | Self {
153 | min: ud.min,
154 | max: ud.max,
155 | }
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use bevy::prelude::*;
16 | use bevy_rapier3d::prelude::*;
17 | use bevy_skybox_cubemap::SkyboxPlugin;
18 | use xsecurelock_saver::engine::XSecurelockSaverPlugins;
19 |
20 | mod config;
21 | mod model;
22 | mod skyboxes;
23 | mod statustracker;
24 | mod storage;
25 | mod world;
26 | mod worldgenerator;
27 |
28 | fn main() {
29 | App::build()
30 | .insert_resource(Msaa { samples: 4 })
31 | .add_plugins(XSecurelockSaverPlugins)
32 | .add_plugin(SkyboxPlugin)
33 | .add_plugin(RapierPhysicsPlugin::::default())
34 | .add_plugin(config::ConfigPlugin)
35 | .add_state(SaverState::Generate)
36 | .add_plugin(storage::StoragePlugin)
37 | .add_plugin(worldgenerator::WorldGeneratorPlugin)
38 | .add_plugin(statustracker::ScoringPlugin)
39 | .add_plugin(world::WorldPlugin)
40 | .add_plugin(skyboxes::SkyboxesPlugin)
41 | .run();
42 | }
43 |
44 | /// Game state of the generator.
45 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
46 | enum SaverState {
47 | /// Loading state, world will be replaced.
48 | Generate,
49 | /// Run the game.
50 | Run,
51 | }
52 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/model.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! Model of the start-state of the world. Identifies a unique world.
16 | use std::f32::consts::PI;
17 |
18 | use bevy::prelude::*;
19 | use serde::{Deserialize, Serialize};
20 |
21 | #[derive(Debug)]
22 | pub struct Scenario {
23 | /// The name of this scenario.
24 | pub id: u64,
25 | /// The family of this scenario. This is the ID of the root of its family tree.
26 | pub family: u64,
27 | /// Optional parent of this scenario. The parent scenario may have been pruned. This is None if
28 | /// the scenario is a root, but is retained even if the parent is pruned.
29 | pub parent: Option,
30 | /// The generation number of this scenario. Useful in case any of the parents have been pruned.
31 | pub generation: u64,
32 | /// The state of the world at the start of the scenario.
33 | pub world: World,
34 | /// The score that this world earned when tested.
35 | pub score: f64,
36 | }
37 |
38 | #[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
39 | pub struct World {
40 | pub planets: Vec,
41 | }
42 |
43 | impl World {
44 | /// Combines overlapping planets into a single, larger planet.
45 | pub fn merge_overlapping_planets(&mut self) {
46 | loop {
47 | // Stop looping when we haven't merged any more planets.
48 | let mut clean = true;
49 |
50 | let mut left = 0;
51 | while left < self.planets.len() - 1 {
52 | let mut right = left + 1;
53 | while right < self.planets.len() {
54 | let total_radius = self.planets[left].radius() + self.planets[right].radius();
55 | let total_radius_sqr = total_radius * total_radius;
56 | let dist_sqr = self.planets[left]
57 | .position
58 | .distance_squared(self.planets[right].position);
59 | if dist_sqr < total_radius_sqr {
60 | clean = false;
61 | self.merge_planets(left, right);
62 | } else {
63 | right += 1;
64 | }
65 | }
66 | left += 1;
67 | }
68 |
69 | if clean {
70 | break;
71 | }
72 | }
73 | }
74 |
75 | /// Helper function to merge two planets with specified indexes. Combines the right planet into
76 | /// the left, then removes the right planet.
77 | fn merge_planets(&mut self, left: usize, right: usize) {
78 | assert!(left < right);
79 | assert!(right < self.planets.len());
80 | {
81 | let (left_sub, right_sub) = self.planets.split_at_mut(right);
82 | left_sub[left].merge(&right_sub[0]);
83 | }
84 | self.planets.remove(right);
85 | }
86 | }
87 |
88 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
89 | pub struct Planet {
90 | pub position: Vec3,
91 | pub velocity: Vec3,
92 | pub mass: f32,
93 | }
94 |
95 | impl Planet {
96 | /// Assumed density of planets.
97 | pub const DENSITY: f32 = 0.1;
98 |
99 | /// Calculates the radius for a planet of the given mass.
100 | pub fn radius_from_mass(mass: f32) -> f32 {
101 | // Calculate radius as if this planet were a sphere with the given mass and density:
102 | // V = 4/3 * pi * r^3
103 | // M = V * D
104 | // M = 4/3 * pi * r^3 * D
105 | // 3M / (4 * pi * D) = r^3
106 | (3. * mass / (4.0 * PI * Self::DENSITY)).cbrt()
107 | }
108 |
109 | /// Calculates the radius of this planet.
110 | pub fn radius(&self) -> f32 {
111 | Self::radius_from_mass(self.mass)
112 | }
113 |
114 | /// Updates the mass so the planet has the given radius.
115 | #[allow(dead_code)]
116 | pub fn set_radius(&mut self, radius: f32) {
117 | // V = 4/3 * pi * r^3
118 | // M = V * D
119 | // M = 4/3 * pi * r^3 * D
120 | self.mass = 4. / 3. * PI * radius.powi(3) * Self::DENSITY;
121 | }
122 |
123 | /// Merges the given other planet into this one.
124 | fn merge(&mut self, other: &Planet) {
125 | let total_mass = self.mass + other.mass;
126 | // multiplying by mass may give less precision, maybe? So pre-calculate multiplication
127 | // factors.
128 | let self_factor = self.mass / total_mass;
129 | let other_factor = other.mass / total_mass;
130 | // The center of mass.
131 | let net_position = self.position * self_factor + other.position * other_factor;
132 | // Equivalent to calculating total momentum and dividing by mass.
133 | let net_velocity = self.velocity * self_factor + other.velocity * other_factor;
134 | self.position = net_position;
135 | self.velocity = net_velocity;
136 | self.mass = total_mass;
137 | }
138 | }
139 |
140 | #[cfg(test)]
141 | mod tests {
142 | use super::*;
143 | use crate::model::World;
144 |
145 | mod planet_tests {
146 | use super::*;
147 | #[test]
148 | fn test_merge_simple() {
149 | let mut left = Planet {
150 | position: Vec3::new(0., 0., 0.),
151 | velocity: Vec3::new(0., 0., 0.),
152 | mass: 1.,
153 | };
154 | let right = Planet {
155 | position: Vec3::new(1., 0., 0.),
156 | velocity: Vec3::new(0., 0., 0.),
157 | mass: 1.,
158 | };
159 | let expected = Planet {
160 | position: Vec3::new(0.5, 0., 0.),
161 | velocity: Vec3::new(0., 0., 0.),
162 | mass: 2.,
163 | };
164 | left.merge(&right);
165 | assert_eq!(left, expected);
166 | }
167 |
168 | #[test]
169 | fn test_merge_moving() {
170 | let mut left = Planet {
171 | position: Vec3::new(1., -5., 0.),
172 | velocity: Vec3::new(3., 6., 0.),
173 | mass: 8.,
174 | };
175 | let right = Planet {
176 | position: Vec3::new(-9., 2., 0.),
177 | velocity: Vec3::new(-7., -2., 0.),
178 | mass: 24.,
179 | };
180 | let expected = Planet {
181 | position: Vec3::new(-6.5, 0.25, 0.),
182 | velocity: Vec3::new(-4.5, 0., 0.),
183 | mass: 32.,
184 | };
185 | left.merge(&right);
186 | assert_eq!(left, expected);
187 | }
188 |
189 | #[test]
190 | fn test_merge_moving_order_independent() {
191 | let mut left = Planet {
192 | position: Vec3::new(-9., 2., 0.),
193 | velocity: Vec3::new(-7., -2., 0.),
194 | mass: 24.,
195 | };
196 | let right = Planet {
197 | position: Vec3::new(1., -5., 0.),
198 | velocity: Vec3::new(3., 6., 0.),
199 | mass: 8.,
200 | };
201 | let expected = Planet {
202 | position: Vec3::new(-6.5, 0.25, 0.),
203 | velocity: Vec3::new(-4.5, 0., 0.),
204 | mass: 32.,
205 | };
206 | left.merge(&right);
207 | assert_eq!(left, expected);
208 | }
209 | }
210 |
211 | mod world_tests {
212 | use super::*;
213 |
214 | #[test]
215 | fn test_merge_planets_simple() {
216 | let mut world = World {
217 | planets: vec![
218 | Planet {
219 | position: Vec3::new(0., 0., 0.),
220 | velocity: Vec3::new(0., 0., 0.),
221 | mass: 1.,
222 | },
223 | Planet {
224 | position: Vec3::new(1., -5., 0.),
225 | velocity: Vec3::new(3., 6., 0.),
226 | mass: 8.,
227 | },
228 | Planet {
229 | position: Vec3::new(1., 0., 0.),
230 | velocity: Vec3::new(0., 0., 0.),
231 | mass: 1.,
232 | },
233 | Planet {
234 | position: Vec3::new(-9., 2., 0.),
235 | velocity: Vec3::new(-7., -2., 0.),
236 | mass: 24.,
237 | },
238 | ],
239 | };
240 | let expected = World {
241 | planets: vec![
242 | Planet {
243 | position: Vec3::new(0., 0., 0.),
244 | velocity: Vec3::new(0., 0., 0.),
245 | mass: 1.,
246 | },
247 | Planet {
248 | position: Vec3::new(-6.5, 0.25, 0.),
249 | velocity: Vec3::new(-4.5, 0., 0.),
250 | mass: 32.,
251 | },
252 | Planet {
253 | position: Vec3::new(1., 0., 0.),
254 | velocity: Vec3::new(0., 0., 0.),
255 | mass: 1.,
256 | },
257 | ],
258 | };
259 | world.merge_planets(1, 3);
260 | assert_eq!(world, expected);
261 | }
262 |
263 | #[test]
264 | fn test_merge_overlapping_simple() {
265 | let mut world = World {
266 | planets: vec![
267 | Planet {
268 | position: Vec3::new(0., 0., 0.),
269 | velocity: Vec3::new(0., 0., 0.),
270 | mass: 1.,
271 | },
272 | Planet {
273 | position: Vec3::new(2., -10., 0.),
274 | velocity: Vec3::new(3., 6., 0.),
275 | mass: 8.,
276 | },
277 | Planet {
278 | position: Vec3::new(5., 5., 0.),
279 | velocity: Vec3::new(0., 0., 0.),
280 | mass: 1.,
281 | },
282 | Planet {
283 | position: Vec3::new(-2., -12., 0.),
284 | velocity: Vec3::new(-7., -2., 0.),
285 | mass: 24.,
286 | },
287 | ],
288 | };
289 | let expected = World {
290 | planets: vec![
291 | Planet {
292 | position: Vec3::new(0., 0., 0.),
293 | velocity: Vec3::new(0., 0., 0.),
294 | mass: 1.,
295 | },
296 | Planet {
297 | position: Vec3::new(-1., -11.5, 0.),
298 | velocity: Vec3::new(-4.5, 0., 0.),
299 | mass: 32.,
300 | },
301 | Planet {
302 | position: Vec3::new(5., 5., 0.),
303 | velocity: Vec3::new(0., 0., 0.),
304 | mass: 1.,
305 | },
306 | ],
307 | };
308 | world.merge_overlapping_planets();
309 | assert_eq!(world, expected);
310 | }
311 | }
312 | }
313 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/skyboxes.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use bevy::prelude::*;
16 | use bevy_skybox_cubemap::{SkyboxBundle, SkyboxMaterial, SkyboxTextureConversion};
17 | use rand::seq::SliceRandom;
18 |
19 | use crate::SaverState;
20 |
21 | pub struct SkyboxesPlugin;
22 |
23 | impl Plugin for SkyboxesPlugin {
24 | fn build(&self, app: &mut AppBuilder) {
25 | app.init_resource::()
26 | .add_startup_system(setup.system())
27 | .add_system_set(
28 | SystemSet::on_enter(SaverState::Run).with_system(change_skybox.system()),
29 | );
30 | }
31 | }
32 |
33 | #[derive(Default)]
34 | struct Skyboxes(Vec>);
35 |
36 | /// Loads skybox textures.
37 | fn setup(
38 | mut commands: Commands,
39 | asset_server: Res,
40 | mut skyboxes: ResMut,
41 | mut materials: ResMut>,
42 | mut skybox_conversion: ResMut,
43 | ) {
44 | for tex in &[
45 | "skyboxes/1.png",
46 | "skyboxes/2.png",
47 | "skyboxes/3.png",
48 | "skyboxes/4.png",
49 | ] {
50 | let tex = asset_server.load(*tex);
51 | skybox_conversion.make_array(tex.clone());
52 | let mat = materials.add(SkyboxMaterial::from_texture(tex));
53 | skyboxes.0.push(mat);
54 | }
55 |
56 | commands.spawn_bundle(SkyboxBundle::new(choose_skybox(&*skyboxes)));
57 | }
58 |
59 | /// Randomly selects a new skybox texture.
60 | fn change_skybox(mut query: Query<&mut Handle>, skyboxes: Res) {
61 | *query.single_mut().unwrap() = choose_skybox(&*skyboxes);
62 | }
63 |
64 | fn choose_skybox(skyboxes: &Skyboxes) -> Handle {
65 | skyboxes.0.choose(&mut rand::thread_rng()).unwrap().clone()
66 | }
67 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/statustracker/scoring_function/expression_serde.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | mod ser {
16 | use serde::ser::{Serialize, Serializer};
17 |
18 | use crate::statustracker::scoring_function::Expression;
19 |
20 | impl Serialize for Expression {
21 | fn serialize(&self, serializer: S) -> Result
22 | where
23 | S: Serializer,
24 | {
25 | serializer.serialize_str(&format!("{}", self))
26 | }
27 | }
28 | }
29 |
30 | mod de {
31 | use serde::de::{Deserialize, Deserializer, Error, Visitor};
32 | use std::fmt;
33 |
34 | use crate::statustracker::scoring_function::Expression;
35 |
36 | impl<'de> Deserialize<'de> for Expression {
37 | fn deserialize(deserializer: D) -> Result
38 | where
39 | D: Deserializer<'de>,
40 | {
41 | deserializer.deserialize_str(ExpressionVisitor)
42 | }
43 | }
44 |
45 | struct ExpressionVisitor;
46 |
47 | impl<'de> Visitor<'de> for ExpressionVisitor {
48 | type Value = Expression;
49 |
50 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
51 | formatter.write_str("a math expression")
52 | }
53 |
54 | fn visit_i8(self, v: i8) -> Result {
55 | Ok(Expression::Constant(v as f64))
56 | }
57 | fn visit_i16(self, v: i16) -> Result {
58 | Ok(Expression::Constant(v as f64))
59 | }
60 | fn visit_i32(self, v: i32) -> Result {
61 | Ok(Expression::Constant(v as f64))
62 | }
63 | fn visit_i64(self, v: i64) -> Result {
64 | Ok(Expression::Constant(v as f64))
65 | }
66 | fn visit_i128(self, v: i128) -> Result {
67 | Ok(Expression::Constant(v as f64))
68 | }
69 | fn visit_u8(self, v: u8) -> Result {
70 | Ok(Expression::Constant(v as f64))
71 | }
72 | fn visit_u16(self, v: u16) -> Result {
73 | Ok(Expression::Constant(v as f64))
74 | }
75 | fn visit_u32(self, v: u32) -> Result {
76 | Ok(Expression::Constant(v as f64))
77 | }
78 | fn visit_u64(self, v: u64) -> Result {
79 | Ok(Expression::Constant(v as f64))
80 | }
81 | fn visit_u128(self, v: u128) -> Result {
82 | Ok(Expression::Constant(v as f64))
83 | }
84 | fn visit_f32(self, v: f32) -> Result {
85 | Ok(Expression::Constant(v as f64))
86 | }
87 | fn visit_f64(self, v: f64) -> Result {
88 | Ok(Expression::Constant(v))
89 | }
90 |
91 | fn visit_str(self, v: &str) -> Result {
92 | v.parse().map_err(E::custom)
93 | }
94 |
95 | fn visit_string(self, v: String) -> Result {
96 | self.visit_str(&v)
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/statustracker/scoring_function/scoring_function_parser.lalrpop:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use std::num::ParseFloatError;
16 |
17 | use lalrpop_util::ParseError;
18 |
19 | use crate::statustracker::scoring_function::Expression;
20 | use crate::statustracker::scoring_function::BinaryOperator;
21 | use crate::statustracker::scoring_function::UnaryOperator;
22 |
23 | grammar;
24 |
25 | extern {
26 | type Error = (usize, ParseFloatError);
27 | }
28 |
29 | BinaryOpTier: Expression = {
30 | > =>
31 | Expression::BinaryOp(Box::new(lhs), op, Box::new(rhs)),
32 | NextTier,
33 | };
34 |
35 | pub Expression: Expression = BinaryOpTier;
36 | MultiplicationTier: Expression = BinaryOpTier;
37 | PowerTier: Expression = BinaryOpTier;
38 |
39 | UnaryTier: Expression = {
40 | => Expression::UnaryOp(op, Box::new(val)),
41 | "(" ")" => Expression::UnaryOp(op, Box::new(val)),
42 | Term,
43 | };
44 |
45 | Term: Expression = {
46 | "(" ")" => <>,
47 | Atom,
48 | };
49 |
50 | Atom: Expression = {
51 | r"(?i)elapsed" => Expression::Elapsed,
52 | r"(?i)total_mass" => Expression::TotalMass,
53 | r"(?i)mass_count" => Expression::MassCount,
54 | =>?
55 | match val.parse::() {
56 | Ok(value) => Ok(Expression::Constant(value)),
57 | Err(err) => Err(ParseError::User { error: (loc, err) }),
58 | },
59 | };
60 |
61 | AdditiveOperator: BinaryOperator = {
62 | "+" => BinaryOperator::Add,
63 | "-" => BinaryOperator::Subtract,
64 | };
65 |
66 | MultiplicativeOperator: BinaryOperator = {
67 | "*" => BinaryOperator::Multiply,
68 | "/" => BinaryOperator::Divide,
69 | };
70 |
71 | PowerOperator: BinaryOperator = {
72 | "^" => BinaryOperator::Exponent,
73 | };
74 |
75 | UnaryOperator: UnaryOperator = {
76 | "+" => UnaryOperator::Positive,
77 | "-" => UnaryOperator::Negative,
78 | };
79 |
80 | LogOperator: UnaryOperator = {
81 | r"(?i)ln" => UnaryOperator::NaturalLog,
82 | r"(?i)log" => UnaryOperator::Base10Log,
83 | };
84 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/statustracker/scoring_function/transforms.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use crate::statustracker::scoring_function::{BinaryOperator, Expression, UnaryOperator};
16 |
17 | /// A visitor that receives a node from an expression tree.
18 | pub trait Visitor {
19 | /// Visit the given expression subtree, optionally replacing it.
20 | fn visit(&mut self, node: &Expression) -> Option;
21 | }
22 |
23 | impl Visitor for F
24 | where
25 | F: FnMut(&Expression) -> Option,
26 | {
27 | fn visit(&mut self, node: &Expression) -> Option {
28 | self(node)
29 | }
30 | }
31 |
32 | impl Expression {
33 | /// Perform a postorder traversal of the expression tree, running the specified visitor on
34 | /// every node to transform it.
35 | fn transform_postorder(&mut self, visitor: &mut V) {
36 | // Traverse all children first.
37 | match self {
38 | Expression::BinaryOp(lhs, _, rhs) => {
39 | lhs.transform_postorder(visitor);
40 | rhs.transform_postorder(visitor);
41 | }
42 | Expression::UnaryOp(_, value) => value.transform_postorder(visitor),
43 | _ => {}
44 | }
45 | if let Some(replacement) = visitor.visit(self) {
46 | *self = replacement;
47 | }
48 | }
49 |
50 | /// Run a set of simplifications on the expression tree to optimize it slightly by precomputing
51 | /// things that can be precomputed.
52 | pub(super) fn simplify(mut self) -> Self {
53 | self.transform_postorder(&mut precompute_and_remove_useless_operations);
54 | self
55 | }
56 | }
57 |
58 | /// Precompute expressions containing constants and remove certain useless when those changes don't
59 | /// affect NaN propagation.
60 | fn precompute_and_remove_useless_operations(node: &Expression) -> Option {
61 | match node {
62 | Expression::BinaryOp(lhs, op, rhs) => match (&**lhs, op, &**rhs) {
63 | // If both sides are constants, we can always just evaluate it now.
64 | (Expression::Constant(lhs), op, Expression::Constant(rhs)) => {
65 | Some(Expression::Constant(op.eval(*lhs, *rhs)))
66 | }
67 |
68 | // Special case simplifications for when the contents are *not* both constants:
69 | // Note: we avoid optimizations which could hide NaN propagation, such as
70 | // multiplication and division of 0. Normally these always produce 0, but if the other
71 | // operand is NaN they produce NaN.
72 |
73 | // Multiplication Simplifications:
74 | // Multiply By 1 -> Other subtree.
75 | (Expression::Constant(cons), BinaryOperator::Multiply, rhs) if *cons == 1. => {
76 | Some(rhs.clone())
77 | }
78 | (lhs, BinaryOperator::Multiply, Expression::Constant(cons)) if *cons == 1. => {
79 | Some(lhs.clone())
80 | }
81 |
82 | // Division Simplifications:
83 | // Divide by 1 -> Numerator.
84 | (lhs, BinaryOperator::Divide, Expression::Constant(cons)) if *cons == 1. => {
85 | Some(lhs.clone())
86 | }
87 |
88 | // Addition Simplifications:
89 | // Add 0 -> Other subtree.
90 | (Expression::Constant(cons), BinaryOperator::Add, rhs) if *cons == 0. => {
91 | Some(rhs.clone())
92 | }
93 | (lhs, BinaryOperator::Add, Expression::Constant(cons)) if *cons == 0. => {
94 | Some(lhs.clone())
95 | }
96 |
97 | // Subtraction Simplifications:
98 | // Subtract from zero -> Negative other subtree.
99 | (Expression::Constant(cons), BinaryOperator::Subtract, rhs) if *cons == 0. => Some(
100 | Expression::UnaryOp(UnaryOperator::Negative, Box::new(rhs.clone())),
101 | ),
102 | // Subtract zero -> Other subtree.
103 | (lhs, BinaryOperator::Subtract, Expression::Constant(cons)) if *cons == 0. => {
104 | Some(lhs.clone())
105 | }
106 |
107 | // Exponent Simplifications:
108 | // Raised to the power of 1 -> Other subtree.
109 | (lhs, BinaryOperator::Exponent, Expression::Constant(cons)) if *cons == 1. => {
110 | Some(lhs.clone())
111 | }
112 | // Raised to the power of 0 -> Constant 1. Unlike multiplication and division, powf(0)
113 | // returns 1 even for NaN and infinity, probably because this is part of the
114 | // mathematical definition of exponentiation.
115 | (_, BinaryOperator::Exponent, Expression::Constant(cons)) if *cons == 0. => {
116 | Some(Expression::Constant(1.))
117 | }
118 |
119 | // No transforms for anything else.
120 | _ => None,
121 | },
122 | Expression::UnaryOp(op, val) => match (op, &**val) {
123 | // Apply unary operators to constants.
124 | (op, Expression::Constant(val)) => Some(Expression::Constant(op.eval(*val))),
125 |
126 | // Remove positive operators since these are no-ops.
127 | (UnaryOperator::Positive, val) => Some(val.clone()),
128 |
129 | // Remove nested pairs of negative operators.
130 | (UnaryOperator::Negative, Expression::UnaryOp(UnaryOperator::Negative, inner)) => {
131 | Some((**inner).clone())
132 | }
133 |
134 | // No transforms for anything else.
135 | _ => None,
136 | },
137 | _ => None,
138 | }
139 | }
140 |
141 | #[cfg(test)]
142 | mod tests {
143 | use self::Expression::*;
144 | use super::super::tests::*;
145 | use super::super::*;
146 |
147 | #[test]
148 | fn simplify_nop_for_atoms() {
149 | assert_simplify(1.5, 1.5);
150 | assert_simplify(Elapsed, Elapsed);
151 | assert_simplify(TotalMass, TotalMass);
152 | assert_simplify(MassCount, MassCount);
153 | }
154 |
155 | #[test]
156 | fn simplify_constexpr() {
157 | assert_simplify(add(1, 2), 3);
158 | assert_simplify(add(add(8.5, 9.25), add(4, 2)), 8.5 + 9.25 + (4. + 2.));
159 | assert_simplify(exp(mul(2, 3), add(neg(1), 4)), (2. * 3f64).powf(-1. + 4.));
160 | }
161 |
162 | #[test]
163 | fn simplify_const_subexprs() {
164 | assert_simplify(exp(Elapsed, mul(3, 4)), exp(Elapsed, 3 * 4));
165 | assert_simplify(
166 | sub(add(Elapsed, mul(5, 6)), exp(add(1, mul(8, 9)), MassCount)),
167 | sub(add(Elapsed, 5 * 6), exp(1 + 8 * 9, MassCount)),
168 | );
169 | }
170 |
171 | #[test]
172 | fn simplify_nested_negations() {
173 | assert_simplify(neg(pos(neg(neg(4)))), -4.);
174 | assert_simplify(neg(pos(neg(neg(Elapsed)))), neg(Elapsed));
175 | }
176 |
177 | fn assert_simplify, E: Into>(original: O, expected: E) {
178 | assert_eq!(original.into().simplify(), expected.into());
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/storage/mod.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use std::error::Error;
16 | use std::path::PathBuf;
17 |
18 | use bevy::prelude::*;
19 |
20 | use crate::config::database::DatabaseConfig;
21 | use crate::model::{Scenario, World};
22 |
23 | use self::pruner::Pruner;
24 | use self::sqlite::SqliteStorage;
25 |
26 | mod pruner;
27 | pub mod sqlite;
28 |
29 | pub struct StoragePlugin;
30 |
31 | impl Plugin for StoragePlugin {
32 | fn build(&self, app: &mut AppBuilder) {
33 | let dbconfig: DatabaseConfig = app.world().get_resource().cloned().unwrap_or_default();
34 |
35 | if let Some(keep) = dbconfig.max_scenarios_to_keep {
36 | let prune_conn = open_from_conf(dbconfig.database_path.as_ref());
37 | app.insert_resource(Pruner::new(keep, prune_conn))
38 | .insert_resource(PruneTimer(Timer::from_seconds(
39 | dbconfig.prune_interval_seconds as f32,
40 | true,
41 | )))
42 | .add_system(prune_sys.system());
43 | }
44 |
45 | let main_conn = open_from_conf(dbconfig.database_path.as_ref());
46 | app.insert_resource(main_conn);
47 | }
48 | }
49 |
50 | fn open_from_conf(path: Option<&PathBuf>) -> SqliteStorage {
51 | match path {
52 | Some(path) => {
53 | let parent = path.parent().expect("Storage path has no parent");
54 | std::fs::create_dir_all(parent).expect("Could not create storage dir");
55 | SqliteStorage::open(path)
56 | }
57 | None => SqliteStorage::open_in_memory(),
58 | }
59 | .expect("Unable to open storage")
60 | }
61 |
62 | struct PruneTimer(Timer);
63 |
64 | fn prune_sys(time: Res, mut timer: ResMut, mut pruner: ResMut) {
65 | timer.0.tick(time.delta());
66 | if timer.0.finished() {
67 | info!("Triggering prune");
68 | pruner.prune();
69 | }
70 | }
71 |
72 | /// Storage for models.
73 | // TODO(zstewart): fix sqlite storage with some thread local magic so that non-mutating methods can
74 | // use &self instead of &mut self.
75 | pub trait Storage {
76 | /// Add a new root scenario. This scenario is the new root of a family of scenarios.
77 | fn add_root_scenario(&mut self, world: World, score: f64) -> Result>;
78 |
79 | /// Add a new scenario that is the child of the specified scenario
80 | fn add_child_scenario(
81 | &mut self,
82 | world: World,
83 | score: f64,
84 | parent: &Scenario,
85 | ) -> Result>;
86 |
87 | /// Returns the number of scenarios available.
88 | fn num_scenarios(&mut self) -> Result>;
89 |
90 | /// Gets the nth scenario, in order of score (descending, so lower indexes are higher scoring
91 | /// scenarios). May return None if the index is outside the number of scenarios.
92 | fn get_nth_scenario_by_score(&mut self, index: u64)
93 | -> Result, Box>;
94 |
95 | /// Removes the bottom scoring scenarios, keeping up to number_to_keep top scoring scenarios.
96 | /// Returns the number of scenarios pruned.
97 | fn keep_top_scenarios_by_score(&mut self, number_to_keep: u64) -> Result>;
98 | }
99 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/storage/pruner.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use std::sync::mpsc::{self, Sender};
16 | use std::thread::{self, JoinHandle};
17 |
18 | use log::{error, info};
19 |
20 | use super::Storage;
21 |
22 | /// Struct used to shutdown pruning.
23 | pub struct Pruner {
24 | join_handle: Option>,
25 | sender: Option>,
26 | }
27 |
28 | // This is safe because we require &mut Self for all methods that access sender, so sharing &self is
29 | // safe though not useful.
30 | unsafe impl Sync for Pruner {}
31 |
32 | impl Pruner {
33 | /// Creates a pruner running on a remote thread which can be triggered to asynchronously prune scenarios.
34 | pub fn new(number_to_keep: u64, storage: S) -> Pruner
35 | where
36 | S: Storage + Send + 'static,
37 | {
38 | let (sender, recv) = mpsc::channel();
39 | let join_handle = thread::spawn(move || {
40 | let mut storage = storage;
41 | loop {
42 | match recv.recv() {
43 | Ok(()) => {
44 | info!("Pruning scenarios");
45 | match storage.keep_top_scenarios_by_score(number_to_keep) {
46 | Ok(num_pruned) => info!("Pruned {} scenarios", num_pruned),
47 | Err(err) => error!("Falied to prune scenarios: {}", err),
48 | }
49 | }
50 | Err(_) => {
51 | info!("Sending final prune and shutting down.");
52 | match storage.keep_top_scenarios_by_score(number_to_keep) {
53 | Ok(num_pruned) => info!("Pruned {} scenarios", num_pruned),
54 | Err(err) => error!("Falied to prune scenarios: {}", err),
55 | }
56 | break;
57 | }
58 | }
59 | }
60 | });
61 |
62 | Pruner {
63 | join_handle: Some(join_handle),
64 | sender: Some(sender),
65 | }
66 | }
67 |
68 | /// Trigger pruning.
69 | // this has to be mut so that Sender isn't accidentally shared across threads.
70 | pub fn prune(&mut self) {
71 | self.sender
72 | .as_ref()
73 | .unwrap()
74 | .send(())
75 | .expect("Pruner shut down unexpectedly");
76 | }
77 | }
78 |
79 | impl Drop for Pruner {
80 | fn drop(&mut self) {
81 | self.sender.take().unwrap();
82 | self.join_handle
83 | .take()
84 | .unwrap()
85 | .join()
86 | .expect("Remote thread paniced");
87 | info!("Scenario pruner shutdown successfully.");
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/storage/sqlite.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use std::error::Error;
16 | use std::path::Path;
17 |
18 | use rusqlite::types::{
19 | FromSql, FromSqlError, ToSql, ToSqlOutput, Value as SqlValue, ValueRef as SqlValueRef,
20 | };
21 | use rusqlite::{Connection, Error as SqlError, NO_PARAMS};
22 | use serde_json;
23 |
24 | use crate::model::{Scenario, World};
25 | use crate::storage::Storage;
26 |
27 | pub struct SqliteStorage {
28 | conn: Connection,
29 | }
30 |
31 | // This is safe because all methods on SqliteStorage take &mut self, so sharing &self across
32 | // threads is safe (though not useful).
33 | unsafe impl Sync for SqliteStorage {}
34 |
35 | impl SqliteStorage {
36 | pub fn open>(path: P) -> Result {
37 | Connection::open(path).and_then(SqliteStorage::from_conn)
38 | }
39 |
40 | pub fn open_in_memory() -> Result {
41 | Connection::open_in_memory().and_then(SqliteStorage::from_conn)
42 | }
43 |
44 | #[allow(dead_code)]
45 | pub fn open_in_memory_named>(name: N) -> Result {
46 | Connection::open(&format!("file:{}?mode=memory&cache=shared", name.as_ref()))
47 | .and_then(SqliteStorage::from_conn)
48 | }
49 |
50 | fn from_conn(conn: Connection) -> Result {
51 | conn.execute(
52 | "CREATE TABLE IF NOT EXISTS scenario (
53 | id INTEGER PRIMARY KEY,
54 | family INTEGER NOT NULL,
55 | parent INTEGER,
56 | generation INTEGER NOT NULL,
57 | world TEXT NOT NULL,
58 | score REAL NOT NULL
59 | )",
60 | NO_PARAMS,
61 | )?;
62 | conn.execute(
63 | "CREATE INDEX IF NOT EXISTS scenario_score_index
64 | ON scenario (
65 | score DESC,
66 | id ASC
67 | )
68 | ",
69 | NO_PARAMS,
70 | )?;
71 | Ok(SqliteStorage { conn })
72 | }
73 | }
74 |
75 | /// Default is required for Specs resources. Default SqliteStorage just runs open_in_memory.
76 | impl Default for SqliteStorage {
77 | fn default() -> Self {
78 | SqliteStorage::open_in_memory().unwrap()
79 | }
80 | }
81 |
82 | impl Storage for SqliteStorage {
83 | fn add_root_scenario(&mut self, world: World, score: f64) -> Result> {
84 | let txn = self.conn.transaction()?;
85 | let inserted = txn.execute(
86 | "INSERT INTO scenario (family, parent, generation, world, score)
87 | VALUES (?1, ?2, ?3, ?4, ?5)",
88 | &[&-1i64 as &dyn ToSql, &None::, &0i64, &world, &score],
89 | )?;
90 | if inserted != 1 {
91 | return Err(
92 | format!("Expected to insert 1 row but had {} row changes", inserted).into(),
93 | );
94 | }
95 | let id = txn.last_insert_rowid();
96 | let updated = txn.execute("UPDATE scenario SET family = ?1 WHERE id = ?1", &[&id])?;
97 | if updated != 1 {
98 | return Err(format!("Expected to update 1 row but had {} row changes", updated).into());
99 | }
100 | txn.commit()?;
101 | Ok(Scenario {
102 | id: id as u64,
103 | family: id as u64,
104 | parent: None,
105 | generation: 0,
106 | world,
107 | score,
108 | })
109 | }
110 |
111 | fn add_child_scenario(
112 | &mut self,
113 | world: World,
114 | score: f64,
115 | parent: &Scenario,
116 | ) -> Result> {
117 | let generation = parent.generation + 1;
118 | let inserted = self.conn.execute(
119 | "INSERT INTO scenario (family, parent, generation, world, score)
120 | VALUES (?1, ?2, ?3, ?4, ?5)",
121 | &[
122 | &SqlWrappingU64(parent.family) as &dyn ToSql,
123 | &Some(SqlWrappingU64(parent.id)),
124 | &SqlBoundedU64(generation),
125 | &world,
126 | &score,
127 | ],
128 | )?;
129 | if inserted != 1 {
130 | return Err(
131 | format!("Expected to insert 1 row but had {} row changes", inserted).into(),
132 | );
133 | }
134 | let id = self.conn.last_insert_rowid() as u64;
135 | Ok(Scenario {
136 | id,
137 | family: parent.family,
138 | parent: Some(parent.id),
139 | generation,
140 | world,
141 | score,
142 | })
143 | }
144 |
145 | fn num_scenarios(&mut self) -> Result> {
146 | self.conn
147 | .query_row_and_then("SELECT COUNT(*) FROM scenario", NO_PARAMS, |row| {
148 | Ok(row.get_checked::<_, SqlBoundedU64>(0)?.0)
149 | })
150 | }
151 |
152 | fn get_nth_scenario_by_score(
153 | &mut self,
154 | index: u64,
155 | ) -> Result, Box> {
156 | let query_result = self.conn.query_row_and_then(
157 | "SELECT id, family, parent, generation, world, score
158 | FROM scenario
159 | ORDER BY score DESC,
160 | id ASC
161 | LIMIT 1
162 | OFFSET ?",
163 | &[&SqlBoundedU64(index)],
164 | |row| {
165 | Ok(Scenario {
166 | id: row.get_checked::<_, SqlWrappingU64>(0)?.0,
167 | family: row.get_checked::<_, SqlWrappingU64>(1)?.0,
168 | parent: row
169 | .get_checked::<_, Option>(2)?
170 | .map(|v| v.0),
171 | generation: row.get_checked::<_, SqlBoundedU64>(3)?.0,
172 | world: row.get_checked(4)?,
173 | score: row.get_checked(5)?,
174 | })
175 | },
176 | );
177 | match query_result {
178 | Ok(scenario) => Ok(Some(scenario)),
179 | Err(SqlError::QueryReturnedNoRows) => Ok(None),
180 | Err(any_other_error) => Err(any_other_error.into()),
181 | }
182 | }
183 |
184 | fn keep_top_scenarios_by_score(&mut self, number_to_keep: u64) -> Result> {
185 | Ok(self.conn.execute(
186 | "DELETE
187 | FROM scenario
188 | WHERE id NOT IN (
189 | SELECT id
190 | FROM scenario
191 | ORDER BY score DESC,
192 | id ASC
193 | LIMIT ?
194 | )",
195 | &[&SqlBoundedU64(number_to_keep)],
196 | )? as u64)
197 | }
198 | }
199 |
200 | /// Struct for serializing u64 in Sql, wrapping out of range i64 values.
201 | struct SqlWrappingU64(u64);
202 |
203 | impl ToSql for SqlWrappingU64 {
204 | fn to_sql(&self) -> Result {
205 | Ok(ToSqlOutput::Owned(SqlValue::Integer(self.0 as i64)))
206 | }
207 | }
208 |
209 | impl FromSql for SqlWrappingU64 {
210 | fn column_result(value: SqlValueRef) -> Result {
211 | match value {
212 | SqlValueRef::Integer(value) => Ok(SqlWrappingU64(value as u64)),
213 | _ => Err(FromSqlError::InvalidType),
214 | }
215 | }
216 | }
217 |
218 | /// Struct for serializing u64 in Sql, clamping at bounds.
219 | struct SqlBoundedU64(u64);
220 |
221 | impl ToSql for SqlBoundedU64 {
222 | fn to_sql(&self) -> Result {
223 | if self.0 <= i64::max_value() as u64 {
224 | Ok(ToSqlOutput::Owned(SqlValue::Integer(self.0 as i64)))
225 | } else {
226 | Err(SqlError::ToSqlConversionFailure(
227 | format!(
228 | "Value {} is too large for SQLite, max is {}",
229 | self.0,
230 | i64::max_value(),
231 | )
232 | .into(),
233 | ))
234 | }
235 | }
236 | }
237 |
238 | impl FromSql for SqlBoundedU64 {
239 | fn column_result(value: SqlValueRef) -> Result {
240 | match value {
241 | SqlValueRef::Integer(value) if value >= 0 => Ok(SqlBoundedU64(value as u64)),
242 | SqlValueRef::Integer(out_of_range) => Err(FromSqlError::OutOfRange(out_of_range)),
243 | _ => Err(FromSqlError::InvalidType),
244 | }
245 | }
246 | }
247 |
248 | impl ToSql for World {
249 | fn to_sql(&self) -> Result {
250 | match serde_json::to_string(self) {
251 | Ok(s) => Ok(ToSqlOutput::Owned(SqlValue::Text(s))),
252 | Err(err) => Err(SqlError::ToSqlConversionFailure(err.into())),
253 | }
254 | }
255 | }
256 |
257 | impl FromSql for World {
258 | fn column_result(value: SqlValueRef) -> Result {
259 | let serialized = match value {
260 | SqlValueRef::Text(serialized) => serialized,
261 | _ => return Err(FromSqlError::InvalidType),
262 | };
263 | serde_json::from_str(serialized).map_err(|err| FromSqlError::Other(err.into()))
264 | }
265 | }
266 |
267 | #[cfg(test)]
268 | mod tests {
269 | use bevy::prelude::*;
270 |
271 | use super::*;
272 | use crate::model::{Planet, World};
273 |
274 | #[test]
275 | fn test_open_in_memory() {
276 | SqliteStorage::open_in_memory().unwrap();
277 | }
278 |
279 | #[test]
280 | fn test_creates_index() {
281 | let storage = SqliteStorage::open_in_memory().unwrap();
282 | let (idxname, unique): (String, bool) = storage
283 | .conn
284 | .query_row("PRAGMA INDEX_LIST('scenario')", NO_PARAMS, |row| {
285 | (row.get(1), row.get(2))
286 | })
287 | .unwrap();
288 | assert_eq!(idxname, "scenario_score_index");
289 | assert!(!unique);
290 | }
291 |
292 | #[test]
293 | fn test_open_in_memory_not_shared() {
294 | let mut first = SqliteStorage::open_in_memory().unwrap();
295 | let mut second = SqliteStorage::open_in_memory().unwrap();
296 |
297 | assert_eq!(first.num_scenarios().unwrap(), 0);
298 | assert_eq!(second.num_scenarios().unwrap(), 0);
299 | first
300 | .add_root_scenario(World { planets: vec![] }, 0.)
301 | .unwrap();
302 | assert_eq!(first.num_scenarios().unwrap(), 1);
303 | assert_eq!(second.num_scenarios().unwrap(), 0);
304 | }
305 |
306 | #[test]
307 | fn test_open_in_memory_named_shared() {
308 | let mut first = SqliteStorage::open_in_memory_named("common").unwrap();
309 | let mut second = SqliteStorage::open_in_memory_named("common").unwrap();
310 |
311 | assert_eq!(first.num_scenarios().unwrap(), 0);
312 | assert_eq!(second.num_scenarios().unwrap(), 0);
313 | first
314 | .add_root_scenario(World { planets: vec![] }, 0.)
315 | .unwrap();
316 | assert_eq!(first.num_scenarios().unwrap(), 1);
317 | assert_eq!(second.num_scenarios().unwrap(), 1);
318 | }
319 |
320 | #[test]
321 | fn test_open_in_memory_named_not_shared() {
322 | let mut first = SqliteStorage::open_in_memory_named("thing1").unwrap();
323 | let mut second = SqliteStorage::open_in_memory_named("thing2").unwrap();
324 |
325 | assert_eq!(first.num_scenarios().unwrap(), 0);
326 | assert_eq!(second.num_scenarios().unwrap(), 0);
327 | first
328 | .add_root_scenario(World { planets: vec![] }, 0.)
329 | .unwrap();
330 | assert_eq!(first.num_scenarios().unwrap(), 1);
331 | assert_eq!(second.num_scenarios().unwrap(), 0);
332 | }
333 |
334 | #[test]
335 | fn test_add_root() {
336 | let mut storage = SqliteStorage::open_in_memory().unwrap();
337 | let world = World {
338 | planets: vec![Planet {
339 | position: Vec3::new(0., 0., 0.),
340 | velocity: Vec3::new(0., 0., 0.),
341 | mass: 1.,
342 | }],
343 | };
344 | let scenario = storage.add_root_scenario(world.clone(), 54.).unwrap();
345 | assert_eq!(scenario.id, scenario.family);
346 | assert_eq!(scenario.parent, None);
347 | assert_eq!(scenario.generation, 0);
348 | assert_eq!(scenario.world, world);
349 | assert_eq!(scenario.score, 54.);
350 |
351 | let values: (i64, i64, Option, i64, World, f64) = storage
352 | .conn
353 | .query_row(
354 | "SELECT id, family, parent, generation, world, score
355 | FROM scenario
356 | WHERE id = ?1",
357 | &[&(scenario.id as i64)],
358 | |row| {
359 | (
360 | row.get(0),
361 | row.get(1),
362 | row.get(2),
363 | row.get(3),
364 | row.get(4),
365 | row.get(5),
366 | )
367 | },
368 | )
369 | .unwrap();
370 | assert_eq!(
371 | values,
372 | (
373 | scenario.id as i64,
374 | scenario.id as i64,
375 | None,
376 | 0i64,
377 | world,
378 | 54.
379 | )
380 | );
381 | }
382 |
383 | #[test]
384 | fn test_add_child() {
385 | let mut storage = SqliteStorage::open_in_memory().unwrap();
386 | let parent = Scenario {
387 | id: 34,
388 | family: 87,
389 | parent: Some(60),
390 | generation: 10,
391 | world: World { planets: vec![] },
392 | score: 3609.,
393 | };
394 | let world = World {
395 | planets: vec![Planet {
396 | position: Vec3::new(0., 0., 0.),
397 | velocity: Vec3::new(0., 0., 0.),
398 | mass: 1.,
399 | }],
400 | };
401 | let scenario = storage
402 | .add_child_scenario(world.clone(), 987., &parent)
403 | .unwrap();
404 | assert_eq!(scenario.family, parent.family);
405 | assert_eq!(scenario.parent, Some(parent.id));
406 | assert_eq!(scenario.generation, parent.generation + 1);
407 | assert_eq!(scenario.world, world);
408 | assert_eq!(scenario.score, 987.);
409 |
410 | let values: (i64, i64, Option, i64, World, f64) = storage
411 | .conn
412 | .query_row(
413 | "SELECT id, family, parent, generation, world, score
414 | FROM scenario
415 | WHERE id = ?1",
416 | &[&(scenario.id as i64)],
417 | |row| {
418 | (
419 | row.get(0),
420 | row.get(1),
421 | row.get(2),
422 | row.get(3),
423 | row.get(4),
424 | row.get(5),
425 | )
426 | },
427 | )
428 | .unwrap();
429 | assert_eq!(
430 | values,
431 | (
432 | scenario.id as i64,
433 | parent.family as i64,
434 | Some(parent.id as i64),
435 | (parent.generation + 1) as i64,
436 | world,
437 | 987.
438 | ),
439 | );
440 | }
441 |
442 | #[test]
443 | fn test_num_scenarios_empty() {
444 | let mut storage = SqliteStorage::open_in_memory().unwrap();
445 | assert_eq!(storage.num_scenarios().unwrap(), 0);
446 | }
447 |
448 | #[test]
449 | fn test_num_scenarios() {
450 | let mut storage = SqliteStorage::open_in_memory().unwrap();
451 | let world1 = World {
452 | planets: vec![Planet {
453 | position: Vec3::new(0., 0., 0.),
454 | velocity: Vec3::new(0., 0., 0.),
455 | mass: 1.,
456 | }],
457 | };
458 | let world2 = World { planets: vec![] };
459 | let world3 = World {
460 | planets: vec![Planet {
461 | position: Vec3::new(80., 0., 0.),
462 | velocity: Vec3::new(25., 30., 0.),
463 | mass: 15.,
464 | }],
465 | };
466 |
467 | {
468 | let mut add_row = storage
469 | .conn
470 | .prepare(
471 | "INSERT INTO scenario (family, parent, generation, world, score)
472 | VALUES (?1, ?2, ?3, ?4, ?5)",
473 | )
474 | .unwrap();
475 | add_row
476 | .execute::<&[&dyn ToSql]>(&[&36i64, &Some(54i64), &10i64, &world1, &90f64])
477 | .unwrap();
478 | add_row
479 | .execute::<&[&dyn ToSql]>(&[&580i64, &Some(908i64), &5i64, &world2, &763f64])
480 | .unwrap();
481 | add_row
482 | .execute::<&[&dyn ToSql]>(&[&170i64, &None::, &32i64, &world3, &66f64])
483 | .unwrap();
484 | add_row
485 | .execute::<&[&dyn ToSql]>(&[&80i64, &Some(6i64), &15i64, &world2, &90f64])
486 | .unwrap();
487 | }
488 |
489 | assert_eq!(storage.num_scenarios().unwrap(), 4);
490 | }
491 |
492 | #[test]
493 | fn test_get_nth_scenario_by_score() {
494 | let mut storage = SqliteStorage::open_in_memory().unwrap();
495 | let world1 = World {
496 | planets: vec![Planet {
497 | position: Vec3::new(0., 0., 0.),
498 | velocity: Vec3::new(0., 0., 0.),
499 | mass: 1.,
500 | }],
501 | };
502 | let world2 = World { planets: vec![] };
503 | let world3 = World {
504 | planets: vec![Planet {
505 | position: Vec3::new(80., 0., 0.),
506 | velocity: Vec3::new(25., 30., 0.),
507 | mass: 15.,
508 | }],
509 | };
510 |
511 | {
512 | let mut add_row = storage
513 | .conn
514 | .prepare(
515 | "INSERT INTO scenario (family, parent, generation, world, score)
516 | VALUES (?1, ?2, ?3, ?4, ?5)",
517 | )
518 | .unwrap();
519 | add_row
520 | .execute::<&[&dyn ToSql]>(&[&36i64, &Some(54i64), &10i64, &world1, &90f64])
521 | .unwrap();
522 | add_row
523 | .execute::<&[&dyn ToSql]>(&[&580i64, &Some(908i64), &5i64, &world2, &763f64])
524 | .unwrap();
525 | add_row
526 | .execute::<&[&dyn ToSql]>(&[&170i64, &None::, &32i64, &world3, &66f64])
527 | .unwrap();
528 | add_row
529 | .execute::<&[&dyn ToSql]>(&[&80i64, &Some(6i64), &15i64, &world2, &90f64])
530 | .unwrap();
531 | }
532 |
533 | let scenario = storage.get_nth_scenario_by_score(0).unwrap().unwrap();
534 | assert_eq!(scenario.family, 580);
535 | assert_eq!(scenario.parent, Some(908));
536 | assert_eq!(scenario.generation, 5);
537 | assert_eq!(scenario.world, world2);
538 | assert_eq!(scenario.score, 763.);
539 |
540 | let scenario = storage.get_nth_scenario_by_score(1).unwrap().unwrap();
541 | assert_eq!(scenario.family, 36);
542 | assert_eq!(scenario.parent, Some(54));
543 | assert_eq!(scenario.generation, 10);
544 | assert_eq!(scenario.world, world1);
545 | assert_eq!(scenario.score, 90.);
546 |
547 | let scenario = storage.get_nth_scenario_by_score(2).unwrap().unwrap();
548 | assert_eq!(scenario.family, 80);
549 | assert_eq!(scenario.parent, Some(6));
550 | assert_eq!(scenario.generation, 15);
551 | assert_eq!(scenario.world, world2);
552 | assert_eq!(scenario.score, 90.);
553 |
554 | let scenario = storage.get_nth_scenario_by_score(3).unwrap().unwrap();
555 | assert_eq!(scenario.family, 170);
556 | assert_eq!(scenario.parent, None);
557 | assert_eq!(scenario.generation, 32);
558 | assert_eq!(scenario.world, world3);
559 | assert_eq!(scenario.score, 66.);
560 |
561 | assert!(storage.get_nth_scenario_by_score(4).unwrap().is_none());
562 | }
563 |
564 | #[test]
565 | fn prune_bottom_scenarios() {
566 | let mut storage = SqliteStorage::open_in_memory().unwrap();
567 | let world1 = World {
568 | planets: vec![Planet {
569 | position: Vec3::new(0., 0., 0.),
570 | velocity: Vec3::new(0., 0., 0.),
571 | mass: 1.,
572 | }],
573 | };
574 | let world2 = World { planets: vec![] };
575 | let world3 = World {
576 | planets: vec![Planet {
577 | position: Vec3::new(80., 0., 0.),
578 | velocity: Vec3::new(25., 30., 0.),
579 | mass: 15.,
580 | }],
581 | };
582 |
583 | {
584 | let mut add_row = storage
585 | .conn
586 | .prepare(
587 | "INSERT INTO scenario (family, parent, generation, world, score)
588 | VALUES (?1, ?2, ?3, ?4, ?5)",
589 | )
590 | .unwrap();
591 | add_row
592 | .execute::<&[&dyn ToSql]>(&[&36i64, &Some(54i64), &10i64, &world1, &90f64])
593 | .unwrap();
594 | add_row
595 | .execute::<&[&dyn ToSql]>(&[&580i64, &Some(908i64), &5i64, &world2, &763f64])
596 | .unwrap();
597 | add_row
598 | .execute::<&[&dyn ToSql]>(&[&170i64, &None::, &32i64, &world3, &66f64])
599 | .unwrap();
600 | add_row
601 | .execute::<&[&dyn ToSql]>(&[&80i64, &Some(6i64), &15i64, &world2, &90f64])
602 | .unwrap();
603 | }
604 |
605 | let scenario = storage.get_nth_scenario_by_score(0).unwrap().unwrap();
606 | assert_eq!(scenario.family, 580);
607 | assert_eq!(scenario.parent, Some(908));
608 | assert_eq!(scenario.generation, 5);
609 | assert_eq!(scenario.world, world2);
610 | assert_eq!(scenario.score, 763.);
611 |
612 | let scenario = storage.get_nth_scenario_by_score(1).unwrap().unwrap();
613 | assert_eq!(scenario.family, 36);
614 | assert_eq!(scenario.parent, Some(54));
615 | assert_eq!(scenario.generation, 10);
616 | assert_eq!(scenario.world, world1);
617 | assert_eq!(scenario.score, 90.);
618 |
619 | let scenario = storage.get_nth_scenario_by_score(2).unwrap().unwrap();
620 | assert_eq!(scenario.family, 80);
621 | assert_eq!(scenario.parent, Some(6));
622 | assert_eq!(scenario.generation, 15);
623 | assert_eq!(scenario.world, world2);
624 | assert_eq!(scenario.score, 90.);
625 |
626 | let scenario = storage.get_nth_scenario_by_score(3).unwrap().unwrap();
627 | assert_eq!(scenario.family, 170);
628 | assert_eq!(scenario.parent, None);
629 | assert_eq!(scenario.generation, 32);
630 | assert_eq!(scenario.world, world3);
631 | assert_eq!(scenario.score, 66.);
632 |
633 | assert!(storage.get_nth_scenario_by_score(4).unwrap().is_none());
634 |
635 | assert_eq!(storage.keep_top_scenarios_by_score(3).unwrap(), 1);
636 |
637 | let scenario = storage.get_nth_scenario_by_score(0).unwrap().unwrap();
638 | assert_eq!(scenario.family, 580);
639 | assert_eq!(scenario.parent, Some(908));
640 | assert_eq!(scenario.generation, 5);
641 | assert_eq!(scenario.world, world2);
642 | assert_eq!(scenario.score, 763.);
643 |
644 | let scenario = storage.get_nth_scenario_by_score(1).unwrap().unwrap();
645 | assert_eq!(scenario.family, 36);
646 | assert_eq!(scenario.parent, Some(54));
647 | assert_eq!(scenario.generation, 10);
648 | assert_eq!(scenario.world, world1);
649 | assert_eq!(scenario.score, 90.);
650 |
651 | let scenario = storage.get_nth_scenario_by_score(2).unwrap().unwrap();
652 | assert_eq!(scenario.family, 80);
653 | assert_eq!(scenario.parent, Some(6));
654 | assert_eq!(scenario.generation, 15);
655 | assert_eq!(scenario.world, world2);
656 | assert_eq!(scenario.score, 90.);
657 |
658 | assert!(storage.get_nth_scenario_by_score(3).unwrap().is_none());
659 | assert!(storage.get_nth_scenario_by_score(4).unwrap().is_none());
660 | }
661 | }
662 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/world.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use bevy::prelude::shape;
16 | use bevy::prelude::*;
17 | use bevy::render::camera::PerspectiveProjection;
18 | use bevy_rapier3d::na::{Point3, Vector3};
19 | use bevy_rapier3d::prelude::*;
20 | use rand_distr::{Distribution, Uniform};
21 |
22 | use crate::config::camera::CameraConfig;
23 | use crate::model::Planet as PlanetConfig;
24 | use crate::statustracker::ActiveWorld;
25 | use crate::SaverState;
26 |
27 | /// Plugin handles configuring and executing the world simulation.
28 | pub struct WorldPlugin;
29 |
30 | impl Plugin for WorldPlugin {
31 | fn build(&self, app: &mut AppBuilder) {
32 | app.init_resource::()
33 | .add_startup_system(setup_camera_light.system())
34 | .add_startup_system(remove_rapier_gravity.system())
35 | .add_system(rotate_camera.system())
36 | .add_system_set(
37 | SystemSet::on_enter(SaverState::Run)
38 | .with_system(remove_planets.system().label("remove-old"))
39 | .with_system(spawn_planets.system().after("remove-old")),
40 | )
41 | .add_system(gravity.system());
42 | }
43 | }
44 |
45 | /// Disables the default rapier gravity.
46 | fn remove_rapier_gravity(mut rcfg: ResMut) {
47 | rcfg.gravity = Vector3::zeros();
48 | }
49 |
50 | /// Add a light and a camera.
51 | fn setup_camera_light(mut commands: Commands) {
52 | // light
53 | commands.spawn_bundle(LightBundle {
54 | transform: Transform::from_xyz(0.0, 0.0, 0.0),
55 | light: Light {
56 | depth: 0.1..50_000.0,
57 | range: 10_000.0,
58 | intensity: 10_000_000.0,
59 | ..Default::default()
60 | },
61 | ..Default::default()
62 | });
63 | // camera
64 | commands.spawn_bundle(PerspectiveCameraBundle {
65 | perspective_projection: PerspectiveProjection {
66 | near: 1.0,
67 | far: 20_000.0,
68 | ..Default::default()
69 | },
70 | ..Default::default()
71 | });
72 | }
73 |
74 | /// rotate the camera around the origin.
75 | fn rotate_camera(
76 | mut query: Query<&mut Transform, With>,
77 | time: Res,
78 | config: Res,
79 | ) {
80 | let t = time.seconds_since_startup() as f32 * config.rotation_speed;
81 | for mut camera in query.iter_mut() {
82 | *camera = Transform::from_xyz(t.sin() * config.view_dist, 0.0, t.cos() * config.view_dist)
83 | .looking_at(Vec3::ZERO, Vec3::Y);
84 | }
85 | }
86 |
87 | /// Holds the sphere mesh used to render planets.
88 | struct PlanetMesh(Handle);
89 |
90 | impl FromWorld for PlanetMesh {
91 | fn from_world(world: &mut World) -> Self {
92 | let mesh = world
93 | .get_resource_mut::>()
94 | .unwrap()
95 | .add(Mesh::from(shape::Icosphere {
96 | radius: 1.0,
97 | subdivisions: 2,
98 | }));
99 | Self(mesh)
100 | }
101 | }
102 |
103 | /// Marker component to identify planets for scoring and deletion.
104 | #[derive(Default)]
105 | pub struct Planet;
106 |
107 | /// Marker to apply gravity.
108 | #[derive(Default)]
109 | struct ApplyGravity;
110 |
111 | #[derive(Bundle, Default)]
112 | struct PlanetBundle {
113 | #[bundle]
114 | pbr: PbrBundle,
115 | #[bundle]
116 | rigidbody: RigidBodyBundle,
117 | #[bundle]
118 | collider: ColliderBundle,
119 | sync: RigidBodyPositionSync,
120 | gravity: ApplyGravity,
121 | planet: Planet,
122 | }
123 |
124 | impl PlanetBundle {
125 | fn new_from_planet(
126 | planet: &PlanetConfig,
127 | mesh: Handle,
128 | material: Handle,
129 | ) -> Self {
130 | let radius = planet.radius();
131 | Self {
132 | pbr: PbrBundle {
133 | mesh,
134 | material,
135 | transform: Transform {
136 | translation: planet.position,
137 | rotation: Quat::IDENTITY,
138 | scale: Vec3::new(radius, radius, radius),
139 | },
140 | ..Default::default()
141 | },
142 | rigidbody: RigidBodyBundle {
143 | position: planet.position.into(),
144 | velocity: RigidBodyVelocity {
145 | linvel: planet.velocity.into(),
146 | ..Default::default()
147 | },
148 | ..Default::default()
149 | },
150 | collider: ColliderBundle {
151 | shape: ColliderShape::ball(radius),
152 | mass_properties: ColliderMassProps::Density(PlanetConfig::DENSITY),
153 | ..Default::default()
154 | },
155 | sync: RigidBodyPositionSync::Interpolated { prev_pos: None },
156 | ..Default::default()
157 | }
158 | }
159 | }
160 |
161 | /// Generates a random color, usually fairly bright.
162 | fn generate_random_color() -> Color {
163 | let hue_dist = Uniform::new(0.0, 360.0);
164 | let sat_dist = Uniform::new_inclusive(0.75, 1.0);
165 | let lightness_dist = Uniform::new_inclusive(0.75, 1.0);
166 |
167 | let h = hue_dist.sample(&mut rand::thread_rng());
168 | let s = sat_dist.sample(&mut rand::thread_rng());
169 | let l = lightness_dist.sample(&mut rand::thread_rng());
170 | Color::hsl(h, s, l)
171 | }
172 |
173 | fn spawn_planets(
174 | mut commands: Commands,
175 | world: Res,
176 | mesh: Res,
177 | mut materials: ResMut>,
178 | ) {
179 | for planet in &world.world.planets {
180 | let material = materials.add(generate_random_color().into());
181 | commands.spawn_bundle(PlanetBundle::new_from_planet(
182 | planet,
183 | mesh.0.clone(),
184 | material,
185 | ));
186 | }
187 | }
188 |
189 | /// Removes all planets.
190 | fn remove_planets(mut commands: Commands, query: Query>) {
191 | for planet in query.iter() {
192 | commands.entity(planet).despawn();
193 | }
194 | }
195 |
196 | /// Intermediate accumulator for gravity calculations.
197 | struct Accumulator {
198 | /// Center of mass of the rigidbody.
199 | com: Point3,
200 | /// Mass of the rigidbody.
201 | mass: f32,
202 | /// Accumulated forces.
203 | force: Vector3,
204 | }
205 |
206 | /// Aplies gravity to rigidbodies.
207 | fn gravity(
208 | mut accumulator: Local>,
209 | mut query: Query<(&RigidBodyMassProps, &mut RigidBodyForces), With>,
210 | ) {
211 | const G: f32 = 500.0;
212 |
213 | accumulator.clear();
214 | for (mass, _) in query.iter_mut() {
215 | accumulator.push(Accumulator {
216 | com: mass.world_com,
217 | mass: mass.mass(),
218 | force: Vector3::zeros(),
219 | });
220 | }
221 | for i in 1..accumulator.len() {
222 | let (current, rest) = accumulator.split_at_mut(i);
223 | let current = &mut current[i - 1];
224 | for other in rest {
225 | let diff = other.com - current.com;
226 | let force_magnitude = G * current.mass * other.mass / diff.norm_squared();
227 | if !force_magnitude.is_finite() {
228 | continue;
229 | }
230 | let force_dir = diff.normalize();
231 | let force = force_magnitude * force_dir;
232 | current.force += force;
233 | other.force -= force;
234 | }
235 | }
236 | for ((_, mut force), acc) in query.iter_mut().zip(&*accumulator) {
237 | force.force += acc.force;
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/saver_genetic_orbits/src/worldgenerator.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use std::time::Duration;
16 |
17 | use bevy::ecs::component::Component;
18 | use bevy::prelude::*;
19 | use rand_distr::{Bernoulli, Distribution, Exp, Normal, Uniform};
20 |
21 | use crate::config::generator::{
22 | GeneratorConfig, MutationParameters, NewPlanetParameters, NewWorldParameters,
23 | PlanetMutationParameters,
24 | };
25 | use crate::config::util::{
26 | Distribution as ConfDist, ExponentialDistribution, NormalDistribution, UniformDistribution,
27 | };
28 | use crate::model::{Planet, Scenario, World};
29 | use crate::statustracker::ActiveWorld;
30 | use crate::storage::sqlite::SqliteStorage;
31 | use crate::storage::Storage;
32 |
33 | use super::SaverState;
34 |
35 | /// Configures the world generator.
36 | pub struct WorldGeneratorPlugin;
37 |
38 | impl Plugin for WorldGeneratorPlugin {
39 | fn build(&self, app: &mut AppBuilder) {
40 | app.insert_resource(DelayResume(Timer::new(Duration::from_secs(5), false)))
41 | .add_system_set(
42 | SystemSet::on_enter(SaverState::Generate)
43 | .with_system(generate_world::.system()),
44 | )
45 | .add_system_set(
46 | SystemSet::on_update(SaverState::Generate).with_system(resume.system()),
47 | );
48 | }
49 | }
50 |
51 | /// Generates a new world to run and inserts it into ActiveWorld, then sets the state to Run.
52 | fn generate_world(
53 | config: Res,
54 | mut storage: ResMut,
55 | mut scenario: ResMut,
56 | mut resume: ResMut,
57 | ) {
58 | info!("Generating world");
59 | let parent = pick_parent(&mut *storage, config.create_new_scenario_probability);
60 |
61 | let world = match parent {
62 | Some(ref parent) => generate_child_world(&parent.world, &config.mutation_parameters),
63 | None => generate_new_world(&config.new_world_parameters),
64 | };
65 |
66 | scenario.start(world, parent);
67 |
68 | resume.0.reset();
69 | }
70 |
71 | struct DelayResume(Timer);
72 |
73 | /// Delays returning to run by half a second.
74 | fn resume(mut state: ResMut>, mut timer: ResMut, time: Res) {
75 | timer.0.tick(time.delta());
76 | if timer.0.just_finished() {
77 | if let Err(err) = state.set(SaverState::Run) {
78 | warn!("Failed to switch from generate to run: {:?}", err);
79 | }
80 | }
81 | }
82 |
83 | /// Picks a scenario to mutate or None if a new scenario should be generated.
84 | fn pick_parent(
85 | storage: &mut impl Storage,
86 | create_new_scenario_probability: f64,
87 | ) -> Option {
88 | let num_scenarios = match storage.num_scenarios() {
89 | Ok(0) => {
90 | info!("No existing scenarios to mutate, generating new one by default");
91 | return None;
92 | }
93 | Ok(ns) => ns,
94 | Err(err) => {
95 | error!("Error getting number of scenarios: {}", err);
96 | return None;
97 | }
98 | };
99 | let picked_scenario = select_index(num_scenarios, create_new_scenario_probability);
100 | match storage.get_nth_scenario_by_score(picked_scenario) {
101 | Ok(Some(scenario)) => {
102 | info!(
103 | "Mutating Scenario {} (parent: {:?}, family: {}, generation: {}, score: {}, \
104 | planets: {})",
105 | scenario.id,
106 | scenario.parent,
107 | scenario.family,
108 | scenario.generation,
109 | scenario.score,
110 | scenario.world.planets.len(),
111 | );
112 | Some(scenario)
113 | }
114 | Ok(None) => {
115 | info!("Generating new Scenario");
116 | None
117 | }
118 | Err(err) => {
119 | error!(
120 | "Generating new Scenario because of error fetching scenario {}: {}",
121 | picked_scenario, err,
122 | );
123 | None
124 | }
125 | }
126 | }
127 |
128 | /// Selects a random index from the number of scenarios. The selected index may be out of
129 | /// range. Uses an exponential distribution where the probability of choosing an out of range
130 | /// index (and thus starting a new scenario) is given by the config.
131 | fn select_index(num_items: u64, create_new_scenario_probability: f64) -> u64 {
132 | assert!(num_items > 0);
133 | // The CDF of the exponential distribution is f(x) = 1-e^(-lx). In order to have
134 | // P probability of getting a value in-range, we want to choose l such that
135 | // f(num-scenarios) = P. Therefore we solve for l:
136 | // l = -ln(1 - P) / num-scenarios
137 | let lambda = -(create_new_scenario_probability.ln()) / num_items as f64;
138 | let dist = Exp::new(lambda).unwrap();
139 | dist.sample(&mut rand::thread_rng()) as u64
140 | }
141 |
142 | /// Randomly generate a new world.
143 | fn generate_new_world(params: &NewWorldParameters) -> World {
144 | let num_planets = match params.num_planets_dist {
145 | ConfDist::Exponential(ExponentialDistribution(lambda)) => {
146 | Exp::new(lambda).unwrap().sample(&mut rand::thread_rng()) as usize
147 | }
148 | ConfDist::Normal(NormalDistribution {
149 | mean,
150 | standard_deviation,
151 | }) => Normal::new(mean, standard_deviation)
152 | .unwrap()
153 | .sample(&mut rand::thread_rng())
154 | .round() as usize,
155 | ConfDist::Uniform(UniformDistribution { min, max }) => {
156 | Uniform::new_inclusive(min as usize, max as usize).sample(&mut rand::thread_rng())
157 | }
158 | };
159 | let num_planets = params.num_planets_range.clamp_inclusive(num_planets);
160 | info!("Generating {} planets", num_planets);
161 |
162 | let mut planets = Vec::with_capacity(num_planets);
163 | for _ in 0..num_planets {
164 | planets.push(generate_new_planet(¶ms.planet_parameters));
165 | }
166 |
167 | let mut world = World { planets };
168 | world.merge_overlapping_planets();
169 | info!(
170 | "After overlap cleanup, world had {} planets",
171 | world.planets.len()
172 | );
173 | world
174 | }
175 |
176 | /// Mutate the given parent world to generate a new random world.
177 | fn generate_child_world(parent: &World, params: &MutationParameters) -> World {
178 | let num_planets_to_add = match params.add_planets_dist {
179 | ConfDist::Exponential(ExponentialDistribution(lambda)) => {
180 | Exp::new(lambda).unwrap().sample(&mut rand::thread_rng()) as usize
181 | }
182 | ConfDist::Normal(NormalDistribution {
183 | mean,
184 | standard_deviation,
185 | }) => Normal::new(mean, standard_deviation)
186 | .unwrap()
187 | .sample(&mut rand::thread_rng())
188 | .round() as usize,
189 | ConfDist::Uniform(UniformDistribution { min, max }) => {
190 | Uniform::new_inclusive(min as usize, max as usize).sample(&mut rand::thread_rng())
191 | }
192 | };
193 | let num_planets_to_add = params
194 | .add_planets_limits
195 | .clamp_inclusive(num_planets_to_add);
196 |
197 | let num_planets_to_remove = match params.remove_planets_dist {
198 | ConfDist::Exponential(ExponentialDistribution(lambda)) => {
199 | Exp::new(lambda).unwrap().sample(&mut rand::thread_rng()) as usize
200 | }
201 | ConfDist::Normal(NormalDistribution {
202 | mean,
203 | standard_deviation,
204 | }) => Normal::new(mean, standard_deviation)
205 | .unwrap()
206 | .sample(&mut rand::thread_rng())
207 | .round() as usize,
208 | ConfDist::Uniform(UniformDistribution { min, max }) => {
209 | Uniform::new_inclusive(min as usize, max as usize).sample(&mut rand::thread_rng())
210 | }
211 | };
212 | let num_planets_to_remove = params
213 | .remove_planets_limits
214 | .clamp_inclusive(num_planets_to_remove);
215 | let num_planets_to_remove = parent.planets.len().min(num_planets_to_remove);
216 |
217 | let change_planet_dist = Bernoulli::new(params.fraction_of_planets_to_change).unwrap();
218 |
219 | // Order of changes is remove, modify, add. This is so we don't remove or modify newly
220 | // added planets and don't modify planets that are about to be removed.
221 |
222 | let mut world = parent.clone();
223 |
224 | // Remove:
225 | for _ in 0..num_planets_to_remove {
226 | // panics if start >= end, but this loop doesn't run if planets.len() == 0, so this is
227 | // safe.
228 | let selected = Uniform::new(0, world.planets.len()).sample(&mut rand::thread_rng());
229 | world.planets.remove(selected);
230 | }
231 | info!("Removed {} planets", num_planets_to_remove);
232 |
233 | // Modify
234 | let mut num_modified = 0;
235 | for planet in world.planets.iter_mut() {
236 | if change_planet_dist.sample(&mut rand::thread_rng()) {
237 | mutate_planet(planet, ¶ms.planet_mutation_parameters);
238 | num_modified += 1;
239 | }
240 | }
241 | info!("Modified {} planets", num_modified);
242 |
243 | for _ in 0..num_planets_to_add {
244 | world
245 | .planets
246 | .push(generate_new_planet(¶ms.new_planet_parameters));
247 | }
248 | info!("Added {} planets", num_planets_to_add);
249 |
250 | world.merge_overlapping_planets();
251 | info!(
252 | "After overlap cleanup, world had {} planets",
253 | world.planets.len()
254 | );
255 | world
256 | }
257 |
258 | /// Generates a new randomly sized planet at a random location with random velocity.
259 | fn generate_new_planet(params: &NewPlanetParameters) -> Planet {
260 | let x_dist = Uniform::new_inclusive(params.start_position.x.min, params.start_position.x.max);
261 | let y_dist = Uniform::new_inclusive(params.start_position.y.min, params.start_position.y.max);
262 | let z_dist = Uniform::new_inclusive(params.start_position.z.min, params.start_position.z.max);
263 |
264 | let position = Vec3::new(
265 | x_dist.sample(&mut rand::thread_rng()) as f32,
266 | y_dist.sample(&mut rand::thread_rng()) as f32,
267 | z_dist.sample(&mut rand::thread_rng()) as f32,
268 | );
269 |
270 | let x_velocity_dist = Normal::new(
271 | params.start_velocity.x.mean,
272 | params.start_velocity.x.standard_deviation,
273 | )
274 | .unwrap();
275 | let y_velocity_dist = Normal::new(
276 | params.start_velocity.y.mean,
277 | params.start_velocity.y.standard_deviation,
278 | )
279 | .unwrap();
280 | let z_velocity_dist = Normal::new(
281 | params.start_velocity.z.mean,
282 | params.start_velocity.z.standard_deviation,
283 | )
284 | .unwrap();
285 |
286 | let velocity = Vec3::new(
287 | x_velocity_dist.sample(&mut rand::thread_rng()) as f32,
288 | y_velocity_dist.sample(&mut rand::thread_rng()) as f32,
289 | z_velocity_dist.sample(&mut rand::thread_rng()) as f32,
290 | );
291 |
292 | let mass_dist =
293 | Normal::new(params.start_mass.mean, params.start_mass.standard_deviation).unwrap();
294 | let mass = params
295 | .min_start_mass
296 | .max(mass_dist.sample(&mut rand::thread_rng()) as f32);
297 |
298 | Planet {
299 | position,
300 | velocity,
301 | mass,
302 | }
303 | }
304 |
305 | /// Mutates a planet by making small changes to the mass, position, and velocity.
306 | fn mutate_planet(planet: &mut Planet, params: &PlanetMutationParameters) {
307 | let x_pos_change = Normal::new(
308 | params.position_change.x.mean,
309 | params.position_change.x.standard_deviation,
310 | )
311 | .unwrap()
312 | .sample(&mut rand::thread_rng()) as f32;
313 | let y_pos_change = Normal::new(
314 | params.position_change.y.mean,
315 | params.position_change.y.standard_deviation,
316 | )
317 | .unwrap()
318 | .sample(&mut rand::thread_rng()) as f32;
319 | let z_pos_change = Normal::new(
320 | params.position_change.z.mean,
321 | params.position_change.z.standard_deviation,
322 | )
323 | .unwrap()
324 | .sample(&mut rand::thread_rng()) as f32;
325 |
326 | let x_vel_change = Normal::new(
327 | params.velocity_change.x.mean,
328 | params.velocity_change.x.standard_deviation,
329 | )
330 | .unwrap()
331 | .sample(&mut rand::thread_rng()) as f32;
332 | let y_vel_change = Normal::new(
333 | params.velocity_change.y.mean,
334 | params.velocity_change.y.standard_deviation,
335 | )
336 | .unwrap()
337 | .sample(&mut rand::thread_rng()) as f32;
338 | let z_vel_change = Normal::new(
339 | params.velocity_change.z.mean,
340 | params.velocity_change.z.standard_deviation,
341 | )
342 | .unwrap()
343 | .sample(&mut rand::thread_rng()) as f32;
344 |
345 | let mass_change = match params.mass_change {
346 | ConfDist::Exponential(ExponentialDistribution(lambda)) => {
347 | Exp::new(lambda).unwrap().sample(&mut rand::thread_rng())
348 | }
349 | ConfDist::Normal(NormalDistribution {
350 | mean,
351 | standard_deviation,
352 | }) => Normal::new(mean, standard_deviation)
353 | .unwrap()
354 | .sample(&mut rand::thread_rng()),
355 | ConfDist::Uniform(UniformDistribution { min, max }) => {
356 | Uniform::new_inclusive(min, max).sample(&mut rand::thread_rng())
357 | }
358 | } as f32;
359 |
360 | planet.position.x += x_pos_change;
361 | planet.position.y += y_pos_change;
362 | planet.position.z += z_pos_change;
363 | planet.velocity.x += x_vel_change;
364 | planet.velocity.y += y_vel_change;
365 | planet.velocity.z += z_vel_change;
366 | planet.mass += mass_change;
367 | planet.mass = params.min_mass.max(planet.mass);
368 | }
369 |
--------------------------------------------------------------------------------
/saver_sfmlrect/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/rust
3 |
4 | ### Rust ###
5 | # Generated by Cargo
6 | # will have compiled files and executables
7 | /target/
8 |
9 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
10 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
11 | # Cargo.lock
12 |
13 | # These are backup files generated by rustfmt
14 | **/*.rs.bk
15 |
16 |
17 | # End of https://www.gitignore.io/api/rust
18 |
--------------------------------------------------------------------------------
/saver_sfmlrect/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "saver_sfmlrect"
3 | version = "0.1.0"
4 | authors = ["Zachary Stewart "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | sfml = "0.16"
9 | xsecurelock-saver = { path = "../xsecurelock-saver", features = ["simple"] }
10 |
--------------------------------------------------------------------------------
/saver_sfmlrect/src/main.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use sfml::graphics::{Color, Image, RectangleShape, RenderTarget, Shape, Texture, Transformable};
16 | use sfml::system::{Clock, Time, Vector2f};
17 |
18 | use xsecurelock_saver::simple::Screensaver;
19 |
20 | /// Simple screensaver that shows a rotating rectangle over a rotating textured square.
21 | struct RotatingRectScreensaver<'t> {
22 | /// Clock used to track time.
23 | clock: Clock,
24 | /// Last update time, used to compute dt using clock.
25 | tick: Time,
26 | /// Rectangle shape to draw. Since it has no textures, uses `'static`.
27 | rect: RectangleShape<'static>,
28 | /// Second rectangle, demonstrating use of texture with lifetime. The texture is allocated
29 | /// before `run_saver` so it will outlive the screensaver instance.
30 | tex_rect: RectangleShape<'t>,
31 | }
32 |
33 | impl<'t> RotatingRectScreensaver<'t> {
34 | /// Updates the previous tick time and computes delta time.
35 | fn update_dt(&mut self) -> Time {
36 | let prev = std::mem::replace(&mut self.tick, self.clock.elapsed_time());
37 | self.tick - prev
38 | }
39 | }
40 |
41 | impl<'t> Screensaver for RotatingRectScreensaver<'t> {
42 | fn update(&mut self) {
43 | let dt = self.update_dt().as_seconds();
44 | self.rect.rotate(50.0 * dt);
45 | self.tex_rect.rotate(-20.0 * dt);
46 | }
47 |
48 | fn draw(&self, target: &mut T) {
49 | target.clear(Color::BLACK);
50 | target.draw(&self.rect);
51 | target.draw(&self.tex_rect);
52 | }
53 | }
54 |
55 | fn main() {
56 | let mut img = Image::new(256, 256);
57 | for x in 0..256 {
58 | for y in 0..256 {
59 | img.set_pixel(x, y, Color::rgb(x as u8, 0, y as u8));
60 | }
61 | }
62 | let tex = Texture::from_image(&img).expect("Failed to create texture");
63 |
64 | // Closure can capture references that outlive the screensaver, allowing you to load textures in
65 | // `main` before starting the screensaver, and then reference them from the screensaver
66 | // instance.
67 | xsecurelock_saver::simple::run_saver(|screen_size| {
68 | let center = Vector2f::new(screen_size.x as f32 * 0.5, screen_size.y as f32 * 0.5);
69 |
70 | let mut rect = RectangleShape::with_size(Vector2f::new(500.0, 250.0));
71 | rect.set_fill_color(Color::GREEN);
72 | rect.set_outline_thickness(5.0);
73 | rect.set_outline_color(Color::rgb(0, 128, 0));
74 | rect.set_position(center);
75 | rect.set_origin(rect.size() * 0.5);
76 |
77 | let mut tex_rect = RectangleShape::with_texture(&tex);
78 | tex_rect.set_size((256.0, 256.0));
79 | tex_rect.set_position(center);
80 | tex_rect.set_origin(tex_rect.size() * 0.5);
81 | tex_rect.set_outline_thickness(2.0);
82 | tex_rect.set_outline_color(Color::MAGENTA);
83 |
84 | let clock = Clock::start();
85 | let tick = clock.elapsed_time();
86 | RotatingRectScreensaver {
87 | clock,
88 | tick,
89 | rect,
90 | tex_rect,
91 | }
92 | });
93 | }
94 |
--------------------------------------------------------------------------------
/sigint/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/rust
3 |
4 | ### Rust ###
5 | # Generated by Cargo
6 | # will have compiled files and executables
7 | /target/
8 |
9 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
10 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
11 | # Cargo.lock
12 |
13 | # These are backup files generated by rustfmt
14 | **/*.rs.bk
15 |
16 |
17 | # End of https://www.gitignore.io/api/rust
18 |
--------------------------------------------------------------------------------
/sigint/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "sigint"
3 | version = "0.1.0"
4 | authors = ["Zachary Stewart "]
5 | edition = "2018"
6 |
7 | [dependencies]
8 | libc = "0.2"
9 |
--------------------------------------------------------------------------------
/sigint/src/lib.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | use std::sync::atomic::{AtomicBool, Ordering};
16 |
17 | use libc;
18 |
19 | static INITIALIZED: AtomicBool = AtomicBool::new(false);
20 | static RECEIVED_SIGINT: AtomicBool = AtomicBool::new(false);
21 |
22 | extern "C" fn sigint_handler(_arg: libc::c_int) {
23 | RECEIVED_SIGINT.store(true, Ordering::Relaxed);
24 | }
25 |
26 | #[allow(non_camel_case_types)]
27 | type sighandler_t = extern "C" fn(libc::c_int);
28 |
29 | extern "C" {
30 | fn signal(signum: libc::c_int, handler: sighandler_t) -> sighandler_t;
31 | }
32 |
33 | pub fn received_sigint() -> bool {
34 | RECEIVED_SIGINT.load(Ordering::Relaxed)
35 | }
36 |
37 | pub fn init() {
38 | if !INITIALIZED.swap(true, Ordering::AcqRel) {
39 | unsafe { signal(libc::SIGINT, sigint_handler) };
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "bevy_wgpu_xsecurelock"
3 | version = "0.5.0"
4 | edition = "2018"
5 | authors = [
6 | "Bevy Contributors ",
7 | "Carter Anderson ",
8 | ]
9 | description = "A wgpu render backend for Bevy Engine, hacked to work with xsecurelock"
10 | homepage = "https://github.com/google/xsecurelock-saver-rs"
11 | repository = "https://github.com/google/xsecurelock-saver-rs"
12 | license = "APACHE2"
13 | keywords = ["bevy"]
14 |
15 | [features]
16 | default = ["bevy_winit"]
17 | trace = ["wgpu/trace"]
18 |
19 | [dependencies]
20 | # bevy
21 | bevy_app = "0.5.0"
22 | bevy_asset = "0.5.0"
23 | bevy_core = "0.5.0"
24 | bevy_diagnostic = "0.5.0"
25 | bevy_ecs = "0.5.0"
26 | bevy_render = "0.5.0"
27 | bevy_window = "0.5.0"
28 | bevy_winit = { optional = true, version = "0.5.0" }
29 | bevy_utils = "0.5.0"
30 |
31 | # other
32 | wgpu = "0.7"
33 | futures-lite = "1.4.0"
34 | crossbeam-channel = "0.5.0"
35 | crossbeam-utils = "0.8.1"
36 | parking_lot = "0.11.0"
37 | raw-window-handle = "0.3"
38 | x11 = { version = "2", features = ["xlib"] }
39 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/LICENSE:
--------------------------------------------------------------------------------
1 | Bevy is dual-licensed under either
2 |
3 | * MIT License (docs/LICENSE-MIT or http://opensource.org/licenses/MIT)
4 | * Apache License, Version 2.0 (docs/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
5 |
6 | at your option.
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/src/diagnostic/mod.rs:
--------------------------------------------------------------------------------
1 | mod wgpu_resource_diagnostics_plugin;
2 | pub use wgpu_resource_diagnostics_plugin::WgpuResourceDiagnosticsPlugin;
3 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/src/diagnostic/wgpu_resource_diagnostics_plugin.rs:
--------------------------------------------------------------------------------
1 | use crate::renderer::WgpuRenderResourceContext;
2 | use bevy_app::prelude::*;
3 | use bevy_diagnostic::{Diagnostic, DiagnosticId, Diagnostics};
4 | use bevy_ecs::system::{IntoSystem, Res, ResMut};
5 | use bevy_render::renderer::RenderResourceContext;
6 |
7 | #[derive(Default)]
8 | pub struct WgpuResourceDiagnosticsPlugin;
9 |
10 | impl Plugin for WgpuResourceDiagnosticsPlugin {
11 | fn build(&self, app: &mut AppBuilder) {
12 | app.add_startup_system(Self::setup_system.system())
13 | .add_system(Self::diagnostic_system.system());
14 | }
15 | }
16 |
17 | impl WgpuResourceDiagnosticsPlugin {
18 | pub const BIND_GROUPS: DiagnosticId =
19 | DiagnosticId::from_u128(21302464753369276741568507794995836890);
20 | pub const BIND_GROUP_IDS: DiagnosticId =
21 | DiagnosticId::from_u128(283571569334075937453357861280307923122);
22 | pub const BIND_GROUP_LAYOUTS: DiagnosticId =
23 | DiagnosticId::from_u128(96406067032931216377076410852598331304);
24 | pub const BUFFERS: DiagnosticId =
25 | DiagnosticId::from_u128(133146619577893994787249934474491530491);
26 | pub const RENDER_PIPELINES: DiagnosticId =
27 | DiagnosticId::from_u128(278527620040377353875091478462209885377);
28 | pub const SAMPLERS: DiagnosticId =
29 | DiagnosticId::from_u128(305855369913076220671125671543184691267);
30 | pub const SHADER_MODULES: DiagnosticId =
31 | DiagnosticId::from_u128(287681470908132753275843248383768232237);
32 | pub const SWAP_CHAINS: DiagnosticId =
33 | DiagnosticId::from_u128(199253035828743332241465305105689014605);
34 | pub const SWAP_CHAIN_OUTPUTS: DiagnosticId =
35 | DiagnosticId::from_u128(112048874168736161226721327099863374234);
36 | pub const TEXTURES: DiagnosticId =
37 | DiagnosticId::from_u128(305955424195390184883220102469231911115);
38 | pub const TEXTURE_VIEWS: DiagnosticId =
39 | DiagnosticId::from_u128(257307432866562594739240898780307437578);
40 | pub const WINDOW_SURFACES: DiagnosticId =
41 | DiagnosticId::from_u128(108237028251680341878766034324149135605);
42 |
43 | pub fn setup_system(mut diagnostics: ResMut) {
44 | diagnostics.add(Diagnostic::new(
45 | Self::WINDOW_SURFACES,
46 | "window_surfaces",
47 | 10,
48 | ));
49 |
50 | diagnostics.add(Diagnostic::new(Self::SWAP_CHAINS, "swap_chains", 10));
51 |
52 | diagnostics.add(Diagnostic::new(
53 | Self::SWAP_CHAIN_OUTPUTS,
54 | "swap_chain_outputs",
55 | 10,
56 | ));
57 |
58 | diagnostics.add(Diagnostic::new(Self::BUFFERS, "buffers", 10));
59 |
60 | diagnostics.add(Diagnostic::new(Self::TEXTURES, "textures", 10));
61 |
62 | diagnostics.add(Diagnostic::new(Self::TEXTURE_VIEWS, "texture_views", 10));
63 |
64 | diagnostics.add(Diagnostic::new(Self::SAMPLERS, "samplers", 10));
65 |
66 | diagnostics.add(Diagnostic::new(Self::BIND_GROUP_IDS, "bind_group_ids", 10));
67 | diagnostics.add(Diagnostic::new(Self::BIND_GROUPS, "bind_groups", 10));
68 |
69 | diagnostics.add(Diagnostic::new(
70 | Self::BIND_GROUP_LAYOUTS,
71 | "bind_group_layouts",
72 | 10,
73 | ));
74 |
75 | diagnostics.add(Diagnostic::new(Self::SHADER_MODULES, "shader_modules", 10));
76 |
77 | diagnostics.add(Diagnostic::new(
78 | Self::RENDER_PIPELINES,
79 | "render_pipelines",
80 | 10,
81 | ));
82 | }
83 |
84 | pub fn diagnostic_system(
85 | mut diagnostics: ResMut,
86 | render_resource_context: Res>,
87 | ) {
88 | let render_resource_context = render_resource_context
89 | .downcast_ref::()
90 | .unwrap();
91 |
92 | diagnostics.add_measurement(
93 | Self::WINDOW_SURFACES,
94 | render_resource_context
95 | .resources
96 | .window_surfaces
97 | .read()
98 | .len() as f64,
99 | );
100 |
101 | diagnostics.add_measurement(
102 | Self::SWAP_CHAINS,
103 | render_resource_context
104 | .resources
105 | .window_swap_chains
106 | .read()
107 | .len() as f64,
108 | );
109 |
110 | diagnostics.add_measurement(
111 | Self::SWAP_CHAIN_OUTPUTS,
112 | render_resource_context
113 | .resources
114 | .swap_chain_frames
115 | .read()
116 | .len() as f64,
117 | );
118 |
119 | diagnostics.add_measurement(
120 | Self::BUFFERS,
121 | render_resource_context.resources.buffers.read().len() as f64,
122 | );
123 |
124 | diagnostics.add_measurement(
125 | Self::TEXTURES,
126 | render_resource_context.resources.textures.read().len() as f64,
127 | );
128 |
129 | diagnostics.add_measurement(
130 | Self::TEXTURE_VIEWS,
131 | render_resource_context.resources.texture_views.read().len() as f64,
132 | );
133 |
134 | diagnostics.add_measurement(
135 | Self::SAMPLERS,
136 | render_resource_context.resources.samplers.read().len() as f64,
137 | );
138 |
139 | diagnostics.add_measurement(
140 | Self::BIND_GROUP_IDS,
141 | render_resource_context.resources.bind_groups.read().len() as f64,
142 | );
143 |
144 | let mut bind_group_count = 0;
145 | for bind_group in render_resource_context
146 | .resources
147 | .bind_groups
148 | .read()
149 | .values()
150 | {
151 | bind_group_count += bind_group.bind_groups.len();
152 | }
153 |
154 | diagnostics.add_measurement(Self::BIND_GROUPS, bind_group_count as f64);
155 |
156 | diagnostics.add_measurement(
157 | Self::BIND_GROUP_LAYOUTS,
158 | render_resource_context
159 | .resources
160 | .bind_group_layouts
161 | .read()
162 | .len() as f64,
163 | );
164 |
165 | diagnostics.add_measurement(
166 | Self::SHADER_MODULES,
167 | render_resource_context
168 | .resources
169 | .shader_modules
170 | .read()
171 | .len() as f64,
172 | );
173 |
174 | diagnostics.add_measurement(
175 | Self::RENDER_PIPELINES,
176 | render_resource_context
177 | .resources
178 | .render_pipelines
179 | .read()
180 | .len() as f64,
181 | );
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod diagnostic;
2 | pub mod renderer;
3 | mod wgpu_render_pass;
4 | mod wgpu_renderer;
5 | mod wgpu_resources;
6 | mod wgpu_type_converter;
7 |
8 | use bevy_window::{WindowDescriptor, WindowId};
9 | pub use wgpu_render_pass::*;
10 | pub use wgpu_renderer::*;
11 | pub use wgpu_resources::*;
12 |
13 | use bevy_app::prelude::*;
14 | use bevy_ecs::{
15 | system::{IntoExclusiveSystem, IntoSystem},
16 | world::World,
17 | };
18 | use bevy_render::{
19 | renderer::{shared_buffers_update_system, RenderResourceContext, SharedBuffers},
20 | RenderStage,
21 | };
22 | use futures_lite::future;
23 | use raw_window_handle::{unix::XlibHandle, HasRawWindowHandle, RawWindowHandle};
24 | use renderer::WgpuRenderResourceContext;
25 | use std::{borrow::Cow, env, os::unix::prelude::OsStringExt};
26 |
27 | #[derive(Clone, Copy)]
28 | pub enum WgpuFeature {
29 | DepthClamping,
30 | TextureCompressionBc,
31 | TimestampQuery,
32 | PipelineStatisticsQuery,
33 | MappablePrimaryBuffers,
34 | SampledTextureBindingArray,
35 | SampledTextureArrayDynamicIndexing,
36 | SampledTextureArrayNonUniformIndexing,
37 | UnsizedBindingArray,
38 | MultiDrawIndirect,
39 | MultiDrawIndirectCount,
40 | PushConstants,
41 | AddressModeClampToBorder,
42 | NonFillPolygonMode,
43 | TextureCompressionEtc2,
44 | TextureCompressionAstcLdr,
45 | TextureAdapterSpecificFormatFeatures,
46 | ShaderFloat64,
47 | VertexAttribute64Bit,
48 | }
49 |
50 | #[derive(Default, Clone)]
51 | pub struct WgpuFeatures {
52 | pub features: Vec,
53 | }
54 |
55 | #[derive(Debug, Clone)]
56 | pub struct WgpuLimits {
57 | pub max_bind_groups: u32,
58 | pub max_dynamic_uniform_buffers_per_pipeline_layout: u32,
59 | pub max_dynamic_storage_buffers_per_pipeline_layout: u32,
60 | pub max_sampled_textures_per_shader_stage: u32,
61 | pub max_samplers_per_shader_stage: u32,
62 | pub max_storage_buffers_per_shader_stage: u32,
63 | pub max_storage_textures_per_shader_stage: u32,
64 | pub max_uniform_buffers_per_shader_stage: u32,
65 | pub max_uniform_buffer_binding_size: u32,
66 | pub max_push_constant_size: u32,
67 | }
68 |
69 | impl Default for WgpuLimits {
70 | fn default() -> Self {
71 | let default = wgpu::Limits::default();
72 | WgpuLimits {
73 | max_bind_groups: default.max_bind_groups,
74 | max_dynamic_uniform_buffers_per_pipeline_layout: default
75 | .max_dynamic_uniform_buffers_per_pipeline_layout,
76 | max_dynamic_storage_buffers_per_pipeline_layout: default
77 | .max_dynamic_storage_buffers_per_pipeline_layout,
78 | max_sampled_textures_per_shader_stage: default.max_sampled_textures_per_shader_stage,
79 | max_samplers_per_shader_stage: default.max_samplers_per_shader_stage,
80 | max_storage_buffers_per_shader_stage: default.max_storage_buffers_per_shader_stage,
81 | max_storage_textures_per_shader_stage: default.max_storage_textures_per_shader_stage,
82 | max_uniform_buffers_per_shader_stage: default.max_uniform_buffers_per_shader_stage,
83 | max_uniform_buffer_binding_size: default.max_uniform_buffer_binding_size,
84 | max_push_constant_size: default.max_push_constant_size,
85 | }
86 | }
87 | }
88 |
89 | #[derive(Default)]
90 | pub struct WgpuPlugin;
91 |
92 | impl Plugin for WgpuPlugin {
93 | fn build(&self, app: &mut AppBuilder) {
94 | let render_system = get_wgpu_render_system(app.world_mut());
95 | app.add_system_to_stage(RenderStage::Render, render_system.exclusive_system())
96 | .add_system_to_stage(
97 | RenderStage::PostRender,
98 | shared_buffers_update_system.system(),
99 | );
100 | }
101 | }
102 |
103 | pub fn get_wgpu_render_system(world: &mut World) -> impl FnMut(&mut World) {
104 | let options = world
105 | .get_resource::()
106 | .cloned()
107 | .unwrap_or_else(WgpuOptions::default);
108 | let mut wgpu_renderer = future::block_on(WgpuRenderer::new(options));
109 |
110 | let resource_context = WgpuRenderResourceContext::new(wgpu_renderer.device.clone());
111 | world.insert_resource::>(Box::new(resource_context));
112 | world.insert_resource(SharedBuffers::new(4096));
113 | move |world| {
114 | wgpu_renderer.update(world);
115 | }
116 | }
117 |
118 | #[derive(Default, Clone)]
119 | pub struct WgpuOptions {
120 | pub device_label: Option>,
121 | pub backend: WgpuBackend,
122 | pub power_pref: WgpuPowerOptions,
123 | pub features: WgpuFeatures,
124 | pub limits: WgpuLimits,
125 | }
126 |
127 | #[derive(Clone)]
128 | pub enum WgpuBackend {
129 | Auto,
130 | Vulkan,
131 | Metal,
132 | Dx12,
133 | Dx11,
134 | Gl,
135 | BrowserWgpu,
136 | }
137 |
138 | impl WgpuBackend {
139 | fn from_env() -> Self {
140 | if let Ok(backend) = std::env::var("BEVY_WGPU_BACKEND") {
141 | match backend.to_lowercase().as_str() {
142 | "vulkan" => WgpuBackend::Vulkan,
143 | "metal" => WgpuBackend::Metal,
144 | "dx12" => WgpuBackend::Dx12,
145 | "dx11" => WgpuBackend::Dx11,
146 | "gl" => WgpuBackend::Gl,
147 | "webgpu" => WgpuBackend::BrowserWgpu,
148 | other => panic!("Unknown backend: {}", other),
149 | }
150 | } else {
151 | WgpuBackend::Auto
152 | }
153 | }
154 | }
155 |
156 | impl Default for WgpuBackend {
157 | fn default() -> Self {
158 | Self::from_env()
159 | }
160 | }
161 |
162 | #[derive(Clone)]
163 | pub enum WgpuPowerOptions {
164 | HighPerformance,
165 | Adaptive,
166 | LowPower,
167 | }
168 |
169 | impl Default for WgpuPowerOptions {
170 | fn default() -> Self {
171 | WgpuPowerOptions::HighPerformance
172 | }
173 | }
174 |
175 | /// External X window.
176 | pub struct ExternalXWindow {
177 | display: *mut x11::xlib::Display,
178 | handle: x11::xlib::Window,
179 | pub window_id: WindowId,
180 | }
181 |
182 | unsafe impl Send for ExternalXWindow {}
183 | unsafe impl Sync for ExternalXWindow {}
184 |
185 | impl ExternalXWindow {
186 | /// Open a connection to the X Display attached to the given window.
187 | pub fn new(handle: x11::xlib::Window) -> Self {
188 | let display = env::var_os("DISPLAY").expect("No X11 $DISPLAY set");
189 | let display =
190 | std::ffi::CString::new(display.into_vec()).expect("$DISPLAY was not a valid CString");
191 | let display = unsafe { x11::xlib::XOpenDisplay(display.as_ptr()) };
192 | if display.is_null() {
193 | panic!("Failed to open display");
194 | }
195 | Self {
196 | display,
197 | handle,
198 | window_id: WindowId::primary(),
199 | }
200 | }
201 |
202 | pub fn bevy_window_descriptor(&self) -> WindowDescriptor {
203 | let mut attributes = unsafe { std::mem::zeroed::() };
204 | if unsafe { x11::xlib::XGetWindowAttributes(self.display, self.handle, &mut attributes) }
205 | == 0
206 | {
207 | panic!("Failed to get window attributes");
208 | }
209 |
210 | WindowDescriptor {
211 | width: attributes.width as f32,
212 | height: attributes.height as f32,
213 | resizable: false,
214 | ..Default::default()
215 | }
216 | }
217 | }
218 |
219 | impl Drop for ExternalXWindow {
220 | fn drop(&mut self) {
221 | unsafe { x11::xlib::XCloseDisplay(self.display) };
222 | self.display = std::ptr::null_mut();
223 | }
224 | }
225 |
226 | unsafe impl HasRawWindowHandle for ExternalXWindow {
227 | fn raw_window_handle(&self) -> RawWindowHandle {
228 | RawWindowHandle::Xlib(XlibHandle {
229 | window: self.handle,
230 | display: self.display.cast(),
231 | ..XlibHandle::empty()
232 | })
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/src/renderer/mod.rs:
--------------------------------------------------------------------------------
1 | mod wgpu_render_context;
2 | mod wgpu_render_graph_executor;
3 | mod wgpu_render_resource_context;
4 |
5 | pub use wgpu_render_context::*;
6 | pub use wgpu_render_graph_executor::*;
7 | pub use wgpu_render_resource_context::*;
8 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/src/renderer/wgpu_render_context.rs:
--------------------------------------------------------------------------------
1 | use super::WgpuRenderResourceContext;
2 | use crate::{wgpu_type_converter::WgpuInto, WgpuRenderPass, WgpuResourceRefs};
3 |
4 | use bevy_render::{
5 | pass::{
6 | PassDescriptor, RenderPass, RenderPassColorAttachmentDescriptor,
7 | RenderPassDepthStencilAttachmentDescriptor, TextureAttachment,
8 | },
9 | renderer::{
10 | BufferId, RenderContext, RenderResourceBinding, RenderResourceBindings,
11 | RenderResourceContext, TextureId,
12 | },
13 | texture::Extent3d,
14 | };
15 |
16 | use std::sync::Arc;
17 |
18 | #[derive(Debug, Default)]
19 | pub struct LazyCommandEncoder {
20 | command_encoder: Option,
21 | }
22 |
23 | impl LazyCommandEncoder {
24 | pub fn get_or_create(&mut self, device: &wgpu::Device) -> &mut wgpu::CommandEncoder {
25 | match self.command_encoder {
26 | Some(ref mut command_encoder) => command_encoder,
27 | None => {
28 | self.create(device);
29 | self.command_encoder.as_mut().unwrap()
30 | }
31 | }
32 | }
33 |
34 | pub fn is_some(&self) -> bool {
35 | self.command_encoder.is_some()
36 | }
37 |
38 | pub fn create(&mut self, device: &wgpu::Device) {
39 | let command_encoder =
40 | device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
41 | self.command_encoder = Some(command_encoder);
42 | }
43 |
44 | pub fn take(&mut self) -> Option {
45 | self.command_encoder.take()
46 | }
47 |
48 | pub fn set(&mut self, command_encoder: wgpu::CommandEncoder) {
49 | self.command_encoder = Some(command_encoder);
50 | }
51 | }
52 |
53 | #[derive(Debug)]
54 | pub struct WgpuRenderContext {
55 | pub device: Arc,
56 | pub command_encoder: LazyCommandEncoder,
57 | pub render_resource_context: WgpuRenderResourceContext,
58 | }
59 |
60 | impl WgpuRenderContext {
61 | pub fn new(device: Arc, resources: WgpuRenderResourceContext) -> Self {
62 | WgpuRenderContext {
63 | device,
64 | render_resource_context: resources,
65 | command_encoder: LazyCommandEncoder::default(),
66 | }
67 | }
68 |
69 | /// Consume this context, finalize the current CommandEncoder (if it exists), and take the
70 | /// current WgpuResources. This is intended to be called from a worker thread right before
71 | /// synchronizing with the main thread.
72 | pub fn finish(&mut self) -> Option {
73 | self.command_encoder.take().map(|encoder| encoder.finish())
74 | }
75 | }
76 |
77 | impl RenderContext for WgpuRenderContext {
78 | fn copy_buffer_to_buffer(
79 | &mut self,
80 | source_buffer: BufferId,
81 | source_offset: u64,
82 | destination_buffer: BufferId,
83 | destination_offset: u64,
84 | size: u64,
85 | ) {
86 | self.render_resource_context.copy_buffer_to_buffer(
87 | self.command_encoder.get_or_create(&self.device),
88 | source_buffer,
89 | source_offset,
90 | destination_buffer,
91 | destination_offset,
92 | size,
93 | );
94 | }
95 |
96 | fn copy_buffer_to_texture(
97 | &mut self,
98 | source_buffer: BufferId,
99 | source_offset: u64,
100 | source_bytes_per_row: u32,
101 | destination_texture: TextureId,
102 | destination_origin: [u32; 3],
103 | destination_mip_level: u32,
104 | size: Extent3d,
105 | ) {
106 | self.render_resource_context.copy_buffer_to_texture(
107 | self.command_encoder.get_or_create(&self.device),
108 | source_buffer,
109 | source_offset,
110 | source_bytes_per_row,
111 | destination_texture,
112 | destination_origin,
113 | destination_mip_level,
114 | size,
115 | )
116 | }
117 |
118 | fn copy_texture_to_buffer(
119 | &mut self,
120 | source_texture: TextureId,
121 | source_origin: [u32; 3],
122 | source_mip_level: u32,
123 | destination_buffer: BufferId,
124 | destination_offset: u64,
125 | destination_bytes_per_row: u32,
126 | size: Extent3d,
127 | ) {
128 | self.render_resource_context.copy_texture_to_buffer(
129 | self.command_encoder.get_or_create(&self.device),
130 | source_texture,
131 | source_origin,
132 | source_mip_level,
133 | destination_buffer,
134 | destination_offset,
135 | destination_bytes_per_row,
136 | size,
137 | )
138 | }
139 |
140 | fn copy_texture_to_texture(
141 | &mut self,
142 | source_texture: TextureId,
143 | source_origin: [u32; 3],
144 | source_mip_level: u32,
145 | destination_texture: TextureId,
146 | destination_origin: [u32; 3],
147 | destination_mip_level: u32,
148 | size: Extent3d,
149 | ) {
150 | self.render_resource_context.copy_texture_to_texture(
151 | self.command_encoder.get_or_create(&self.device),
152 | source_texture,
153 | source_origin,
154 | source_mip_level,
155 | destination_texture,
156 | destination_origin,
157 | destination_mip_level,
158 | size,
159 | )
160 | }
161 |
162 | fn resources(&self) -> &dyn RenderResourceContext {
163 | &self.render_resource_context
164 | }
165 |
166 | fn resources_mut(&mut self) -> &mut dyn RenderResourceContext {
167 | &mut self.render_resource_context
168 | }
169 |
170 | fn begin_pass(
171 | &mut self,
172 | pass_descriptor: &PassDescriptor,
173 | render_resource_bindings: &RenderResourceBindings,
174 | run_pass: &mut dyn FnMut(&mut dyn RenderPass),
175 | ) {
176 | if !self.command_encoder.is_some() {
177 | self.command_encoder.create(&self.device);
178 | }
179 | let resource_lock = self.render_resource_context.resources.read();
180 | let refs = resource_lock.refs();
181 | let mut encoder = self.command_encoder.take().unwrap();
182 | {
183 | let render_pass = create_render_pass(
184 | pass_descriptor,
185 | render_resource_bindings,
186 | &refs,
187 | &mut encoder,
188 | );
189 | let mut wgpu_render_pass = WgpuRenderPass {
190 | render_pass,
191 | render_context: self,
192 | wgpu_resources: refs,
193 | pipeline_descriptor: None,
194 | };
195 |
196 | run_pass(&mut wgpu_render_pass);
197 | }
198 |
199 | self.command_encoder.set(encoder);
200 | }
201 | }
202 |
203 | pub fn create_render_pass<'a, 'b>(
204 | pass_descriptor: &PassDescriptor,
205 | global_render_resource_bindings: &'b RenderResourceBindings,
206 | refs: &WgpuResourceRefs<'a>,
207 | encoder: &'a mut wgpu::CommandEncoder,
208 | ) -> wgpu::RenderPass<'a> {
209 | encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
210 | label: None,
211 | color_attachments: &pass_descriptor
212 | .color_attachments
213 | .iter()
214 | .map(|c| {
215 | create_wgpu_color_attachment_descriptor(global_render_resource_bindings, refs, c)
216 | })
217 | .collect::>(),
218 | depth_stencil_attachment: pass_descriptor.depth_stencil_attachment.as_ref().map(|d| {
219 | create_wgpu_depth_stencil_attachment_descriptor(
220 | global_render_resource_bindings,
221 | refs,
222 | d,
223 | )
224 | }),
225 | })
226 | }
227 |
228 | fn get_texture_view<'a>(
229 | global_render_resource_bindings: &RenderResourceBindings,
230 | refs: &WgpuResourceRefs<'a>,
231 | attachment: &TextureAttachment,
232 | ) -> &'a wgpu::TextureView {
233 | match attachment {
234 | TextureAttachment::Name(name) => match global_render_resource_bindings.get(&name) {
235 | Some(RenderResourceBinding::Texture(resource)) => refs.textures.get(&resource).unwrap(),
236 | _ => {
237 | panic!("Color attachment {} does not exist.", name);
238 | }
239 | },
240 | TextureAttachment::Id(render_resource) => refs.textures.get(&render_resource).unwrap_or_else(|| &refs.swap_chain_frames.get(&render_resource).unwrap().output.view),
241 | TextureAttachment::Input(_) => panic!("Encountered unset `TextureAttachment::Input`. The `RenderGraph` executor should always set `TextureAttachment::Inputs` to `TextureAttachment::RenderResource` before running. This is a bug, please report it!"),
242 | }
243 | }
244 |
245 | fn create_wgpu_color_attachment_descriptor<'a>(
246 | global_render_resource_bindings: &RenderResourceBindings,
247 | refs: &WgpuResourceRefs<'a>,
248 | color_attachment_descriptor: &RenderPassColorAttachmentDescriptor,
249 | ) -> wgpu::RenderPassColorAttachmentDescriptor<'a> {
250 | let attachment = get_texture_view(
251 | global_render_resource_bindings,
252 | refs,
253 | &color_attachment_descriptor.attachment,
254 | );
255 |
256 | let resolve_target = color_attachment_descriptor
257 | .resolve_target
258 | .as_ref()
259 | .map(|target| get_texture_view(global_render_resource_bindings, refs, &target));
260 |
261 | wgpu::RenderPassColorAttachmentDescriptor {
262 | ops: (&color_attachment_descriptor.ops).wgpu_into(),
263 | attachment,
264 | resolve_target,
265 | }
266 | }
267 |
268 | fn create_wgpu_depth_stencil_attachment_descriptor<'a>(
269 | global_render_resource_bindings: &RenderResourceBindings,
270 | refs: &WgpuResourceRefs<'a>,
271 | depth_stencil_attachment_descriptor: &RenderPassDepthStencilAttachmentDescriptor,
272 | ) -> wgpu::RenderPassDepthStencilAttachmentDescriptor<'a> {
273 | let attachment = get_texture_view(
274 | global_render_resource_bindings,
275 | refs,
276 | &depth_stencil_attachment_descriptor.attachment,
277 | );
278 |
279 | wgpu::RenderPassDepthStencilAttachmentDescriptor {
280 | attachment,
281 | depth_ops: depth_stencil_attachment_descriptor
282 | .depth_ops
283 | .as_ref()
284 | .map(|ops| ops.wgpu_into()),
285 | stencil_ops: depth_stencil_attachment_descriptor
286 | .stencil_ops
287 | .as_ref()
288 | .map(|ops| ops.wgpu_into()),
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/src/renderer/wgpu_render_graph_executor.rs:
--------------------------------------------------------------------------------
1 | use super::{WgpuRenderContext, WgpuRenderResourceContext};
2 | use bevy_ecs::world::World;
3 | use bevy_render::{
4 | render_graph::{Edge, NodeId, ResourceSlots, StageBorrow},
5 | renderer::RenderResourceContext,
6 | };
7 | use bevy_utils::HashMap;
8 | use parking_lot::RwLock;
9 | use std::sync::Arc;
10 |
11 | #[derive(Debug)]
12 | pub struct WgpuRenderGraphExecutor {
13 | pub max_thread_count: usize,
14 | }
15 |
16 | impl WgpuRenderGraphExecutor {
17 | pub fn execute(
18 | &self,
19 | world: &World,
20 | device: Arc,
21 | queue: &mut wgpu::Queue,
22 | stages: &mut [StageBorrow],
23 | ) {
24 | let render_resource_context = {
25 | let context = world
26 | .get_resource::>()
27 | .unwrap();
28 | context
29 | .downcast_ref::()
30 | .unwrap()
31 | .clone()
32 | };
33 | let node_outputs: Arc>> = Default::default();
34 | for stage in stages.iter_mut() {
35 | // TODO: sort jobs and slice by "amount of work" / weights
36 | // stage.jobs.sort_by_key(|j| j.node_states.len());
37 |
38 | let (sender, receiver) = crossbeam_channel::bounded(self.max_thread_count);
39 | let chunk_size = (stage.jobs.len() + self.max_thread_count - 1) / self.max_thread_count; // divide ints rounding remainder up
40 | let mut actual_thread_count = 0;
41 | // crossbeam_utils::thread::scope(|s| {
42 | for jobs_chunk in stage.jobs.chunks_mut(chunk_size) {
43 | let sender = sender.clone();
44 | let world = &*world;
45 | actual_thread_count += 1;
46 | let device = device.clone();
47 | let render_resource_context = render_resource_context.clone();
48 | let node_outputs = node_outputs.clone();
49 | // s.spawn(move |_| {
50 | let mut render_context = WgpuRenderContext::new(device, render_resource_context);
51 | for job in jobs_chunk.iter_mut() {
52 | for node_state in job.node_states.iter_mut() {
53 | // bind inputs from connected node outputs
54 | for (i, mut input_slot) in node_state.input_slots.iter_mut().enumerate() {
55 | if let Edge::SlotEdge {
56 | output_node,
57 | output_index,
58 | ..
59 | } = node_state.edges.get_input_slot_edge(i).unwrap()
60 | {
61 | let node_outputs = node_outputs.read();
62 | let outputs = if let Some(outputs) = node_outputs.get(output_node) {
63 | outputs
64 | } else {
65 | panic!("Node inputs not set.")
66 | };
67 |
68 | let output_resource =
69 | outputs.get(*output_index).expect("Output should be set.");
70 | input_slot.resource = Some(output_resource);
71 | } else {
72 | panic!("No edge connected to input.")
73 | }
74 | }
75 | node_state.node.update(
76 | world,
77 | &mut render_context,
78 | &node_state.input_slots,
79 | &mut node_state.output_slots,
80 | );
81 |
82 | node_outputs
83 | .write()
84 | .insert(node_state.id, node_state.output_slots.clone());
85 | }
86 | }
87 | sender.send(render_context.finish()).unwrap();
88 | // });
89 | }
90 | // })
91 | // .unwrap();
92 |
93 | let mut command_buffers = Vec::new();
94 | for _i in 0..actual_thread_count {
95 | let command_buffer = receiver.recv().unwrap();
96 | if let Some(command_buffer) = command_buffer {
97 | command_buffers.push(command_buffer);
98 | }
99 | }
100 |
101 | queue.submit(command_buffers.drain(..));
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/src/wgpu_render_pass.rs:
--------------------------------------------------------------------------------
1 | use crate::{renderer::WgpuRenderContext, wgpu_type_converter::WgpuInto, WgpuResourceRefs};
2 | use bevy_asset::Handle;
3 | use bevy_render::{
4 | pass::RenderPass,
5 | pipeline::{BindGroupDescriptorId, IndexFormat, PipelineDescriptor},
6 | renderer::{BindGroupId, BufferId, RenderContext},
7 | };
8 | use bevy_utils::tracing::trace;
9 | use std::ops::Range;
10 |
11 | #[derive(Debug)]
12 | pub struct WgpuRenderPass<'a> {
13 | pub render_pass: wgpu::RenderPass<'a>,
14 | pub render_context: &'a WgpuRenderContext,
15 | pub wgpu_resources: WgpuResourceRefs<'a>,
16 | pub pipeline_descriptor: Option<&'a PipelineDescriptor>,
17 | }
18 |
19 | impl<'a> RenderPass for WgpuRenderPass<'a> {
20 | fn get_render_context(&self) -> &dyn RenderContext {
21 | self.render_context
22 | }
23 |
24 | fn set_vertex_buffer(&mut self, start_slot: u32, buffer_id: BufferId, offset: u64) {
25 | let buffer = self.wgpu_resources.buffers.get(&buffer_id).unwrap();
26 | self.render_pass
27 | .set_vertex_buffer(start_slot, buffer.slice(offset..));
28 | }
29 |
30 | fn set_viewport(&mut self, x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32) {
31 | self.render_pass
32 | .set_viewport(x, y, w, h, min_depth, max_depth);
33 | }
34 |
35 | fn set_scissor_rect(&mut self, x: u32, y: u32, w: u32, h: u32) {
36 | self.render_pass.set_scissor_rect(x, y, w, h);
37 | }
38 |
39 | fn set_stencil_reference(&mut self, reference: u32) {
40 | self.render_pass.set_stencil_reference(reference);
41 | }
42 |
43 | fn set_index_buffer(&mut self, buffer_id: BufferId, offset: u64, index_format: IndexFormat) {
44 | let buffer = self.wgpu_resources.buffers.get(&buffer_id).unwrap();
45 | self.render_pass
46 | .set_index_buffer(buffer.slice(offset..), index_format.wgpu_into());
47 | }
48 |
49 | fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) {
50 | self.render_pass
51 | .draw_indexed(indices, base_vertex, instances);
52 | }
53 |
54 | fn draw(&mut self, vertices: Range, instances: Range) {
55 | self.render_pass.draw(vertices, instances);
56 | }
57 |
58 | fn set_bind_group(
59 | &mut self,
60 | index: u32,
61 | bind_group_descriptor_id: BindGroupDescriptorId,
62 | bind_group: BindGroupId,
63 | dynamic_uniform_indices: Option<&[u32]>,
64 | ) {
65 | if let Some(bind_group_info) = self
66 | .wgpu_resources
67 | .bind_groups
68 | .get(&bind_group_descriptor_id)
69 | {
70 | if let Some(wgpu_bind_group) = bind_group_info.bind_groups.get(&bind_group) {
71 | const EMPTY: &[u32] = &[];
72 | let dynamic_uniform_indices =
73 | if let Some(dynamic_uniform_indices) = dynamic_uniform_indices {
74 | dynamic_uniform_indices
75 | } else {
76 | EMPTY
77 | };
78 | self.wgpu_resources
79 | .used_bind_group_sender
80 | .send(bind_group)
81 | .unwrap();
82 |
83 | trace!(
84 | "set bind group {:?} {:?}: {:?}",
85 | bind_group_descriptor_id,
86 | dynamic_uniform_indices,
87 | bind_group
88 | );
89 | self.render_pass
90 | .set_bind_group(index, wgpu_bind_group, dynamic_uniform_indices);
91 | }
92 | }
93 | }
94 |
95 | fn set_pipeline(&mut self, pipeline_handle: &Handle) {
96 | let pipeline = self
97 | .wgpu_resources
98 | .render_pipelines
99 | .get(pipeline_handle)
100 | .expect(
101 | "Attempted to use a pipeline that does not exist in this `RenderPass`'s `RenderContext`.",
102 | );
103 | self.render_pass.set_pipeline(pipeline);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/src/wgpu_renderer.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | renderer::{WgpuRenderGraphExecutor, WgpuRenderResourceContext},
3 | wgpu_type_converter::WgpuInto,
4 | WgpuBackend, WgpuOptions, WgpuPowerOptions,
5 | };
6 | use bevy_app::{Events, ManualEventReader};
7 | use bevy_ecs::world::{Mut, World};
8 | use bevy_render::{
9 | render_graph::{DependentNodeStager, RenderGraph, RenderGraphStager},
10 | renderer::RenderResourceContext,
11 | };
12 | use bevy_window::{WindowCreated, WindowResized, Windows};
13 | use std::{ops::Deref, sync::Arc};
14 |
15 | pub struct WgpuRenderer {
16 | pub instance: wgpu::Instance,
17 | pub device: Arc,
18 | pub queue: wgpu::Queue,
19 | pub window_resized_event_reader: ManualEventReader,
20 | pub window_created_event_reader: ManualEventReader,
21 | pub initialized: bool,
22 | }
23 |
24 | impl WgpuRenderer {
25 | pub async fn new(options: WgpuOptions) -> Self {
26 | let backend = match options.backend {
27 | WgpuBackend::Auto => wgpu::BackendBit::PRIMARY,
28 | WgpuBackend::Vulkan => wgpu::BackendBit::VULKAN,
29 | WgpuBackend::Metal => wgpu::BackendBit::METAL,
30 | WgpuBackend::Dx12 => wgpu::BackendBit::DX12,
31 | WgpuBackend::Dx11 => wgpu::BackendBit::DX11,
32 | WgpuBackend::Gl => wgpu::BackendBit::GL,
33 | WgpuBackend::BrowserWgpu => wgpu::BackendBit::BROWSER_WEBGPU,
34 | };
35 | let instance = wgpu::Instance::new(backend);
36 |
37 | let adapter = instance
38 | .request_adapter(&wgpu::RequestAdapterOptions {
39 | power_preference: match options.power_pref {
40 | WgpuPowerOptions::HighPerformance => wgpu::PowerPreference::HighPerformance,
41 | WgpuPowerOptions::Adaptive => wgpu::PowerPreference::LowPower,
42 | WgpuPowerOptions::LowPower => wgpu::PowerPreference::LowPower,
43 | },
44 | compatible_surface: None,
45 | })
46 | .await
47 | .expect("Unable to find a GPU! Make sure you have installed required drivers!");
48 |
49 | #[cfg(feature = "trace")]
50 | let trace_path = Some(std::path::Path::new("wgpu_trace"));
51 | #[cfg(not(feature = "trace"))]
52 | let trace_path = None;
53 |
54 | let (device, queue) = adapter
55 | .request_device(
56 | &wgpu::DeviceDescriptor {
57 | label: options.device_label.as_ref().map(|a| a.as_ref()),
58 | features: options.features.wgpu_into(),
59 | limits: options.limits.wgpu_into(),
60 | },
61 | trace_path,
62 | )
63 | .await
64 | .unwrap();
65 | let device = Arc::new(device);
66 | WgpuRenderer {
67 | instance,
68 | device,
69 | queue,
70 | window_resized_event_reader: Default::default(),
71 | window_created_event_reader: Default::default(),
72 | initialized: false,
73 | }
74 | }
75 |
76 | pub fn handle_window_created_events(&mut self, world: &mut World) {
77 | let world = world.cell();
78 | let mut render_resource_context = world
79 | .get_resource_mut::>()
80 | .unwrap();
81 | let render_resource_context = render_resource_context
82 | .downcast_mut::()
83 | .unwrap();
84 | let windows = world.get_resource::().unwrap();
85 | let window_created_events = world.get_resource::>().unwrap();
86 | for window_created_event in self
87 | .window_created_event_reader
88 | .iter(&window_created_events)
89 | {
90 | let window = windows
91 | .get(window_created_event.id)
92 | .expect("Received window created event for non-existent window.");
93 | #[cfg(feature = "bevy_winit")]
94 | {
95 | if let Some(winit_windows) = world.get_resource::() {
96 | let winit_window = winit_windows.get_window(window.id()).unwrap();
97 | let surface = unsafe { self.instance.create_surface(winit_window.deref()) };
98 | render_resource_context.set_window_surface(window.id(), surface);
99 | }
100 | }
101 | if let Some(external_window) = world.get_resource::() {
102 | assert!(window.id() == external_window.window_id);
103 | let surface = unsafe { self.instance.create_surface(&*external_window) };
104 | render_resource_context.set_window_surface(window.id(), surface);
105 | }
106 | }
107 | }
108 |
109 | pub fn run_graph(&mut self, world: &mut World) {
110 | world.resource_scope(|world, mut render_graph: Mut| {
111 | render_graph.prepare(world);
112 | // stage nodes
113 | let mut stager = DependentNodeStager::loose_grouping();
114 | let stages = stager.get_stages(&render_graph).unwrap();
115 | let mut borrowed = stages.borrow(&mut render_graph);
116 |
117 | // execute stages
118 | let graph_executor = WgpuRenderGraphExecutor {
119 | max_thread_count: 2,
120 | };
121 | graph_executor.execute(world, self.device.clone(), &mut self.queue, &mut borrowed);
122 | })
123 | }
124 |
125 | pub fn update(&mut self, world: &mut World) {
126 | self.handle_window_created_events(world);
127 | self.run_graph(world);
128 |
129 | let render_resource_context = world
130 | .get_resource::>()
131 | .unwrap();
132 | render_resource_context.drop_all_swap_chain_textures();
133 | render_resource_context.remove_stale_bind_groups();
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/third_party/bevy_wgpu_xsecurelock/src/wgpu_resources.rs:
--------------------------------------------------------------------------------
1 | use bevy_asset::{Handle, HandleUntyped};
2 | use bevy_render::{
3 | pipeline::{BindGroupDescriptorId, PipelineDescriptor},
4 | renderer::{BindGroupId, BufferId, BufferInfo, RenderResourceId, SamplerId, TextureId},
5 | shader::Shader,
6 | texture::TextureDescriptor,
7 | };
8 | use bevy_utils::HashMap;
9 | use bevy_window::WindowId;
10 | use crossbeam_channel::{Receiver, Sender, TryRecvError};
11 | use parking_lot::{RwLock, RwLockReadGuard};
12 | use std::sync::Arc;
13 |
14 | #[derive(Debug, Default)]
15 | pub struct WgpuBindGroupInfo {
16 | pub bind_groups: HashMap,
17 | }
18 |
19 | /// Grabs a read lock on all wgpu resources. When paired with WgpuResourceRefs, this allows
20 | /// you to pass in wgpu resources to wgpu::RenderPass<'a> with the appropriate lifetime. This is
21 | /// accomplished by grabbing a WgpuResourcesReadLock _before_ creating a wgpu::RenderPass, getting a
22 | /// WgpuResourcesRefs, and storing that in the pass.
23 | ///
24 | /// This is only a problem because RwLockReadGuard.read() erases the guard's lifetime and creates a
25 | /// new anonymous lifetime. If you call RwLockReadGuard.read() during a pass, the reference will
26 | /// have an anonymous lifetime that lives for less than the pass, which violates the lifetime
27 | /// constraints in place.
28 | ///
29 | /// The biggest implication of this design (other than the additional boilerplate here) is that
30 | /// beginning a render pass blocks writes to these resources. This means that if the pass attempts
31 | /// to write any resource, a deadlock will occur. WgpuResourceRefs only has immutable references, so
32 | /// the only way to make a deadlock happen is to access WgpuResources directly in the pass. It also
33 | /// means that other threads attempting to write resources will need to wait for pass encoding to
34 | /// finish. Almost all writes should occur before passes start, so this hopefully won't be a
35 | /// problem.
36 | ///
37 | /// It is worth comparing the performance of this to transactional / copy-based approaches. This
38 | /// lock based design guarantees consistency, doesn't perform redundant allocations, and only blocks
39 | /// when a write is occurring. A copy based approach would never block, but would require more
40 | /// allocations / state-synchronization, which I expect will be more expensive. It would also be
41 | /// "eventually consistent" instead of "strongly consistent".
42 | ///
43 | /// Single threaded implementations don't need to worry about these lifetimes constraints at all.
44 | /// RenderPasses can use a RenderContext's WgpuResources directly. RenderContext already has a
45 | /// lifetime greater than the RenderPass.
46 | #[derive(Debug)]
47 | pub struct WgpuResourcesReadLock<'a> {
48 | pub buffers: RwLockReadGuard<'a, HashMap>>,
49 | pub textures: RwLockReadGuard<'a, HashMap>,
50 | pub swap_chain_frames: RwLockReadGuard<'a, HashMap>,
51 | pub render_pipelines:
52 | RwLockReadGuard<'a, HashMap, wgpu::RenderPipeline>>,
53 | pub bind_groups: RwLockReadGuard<'a, HashMap>,
54 | pub used_bind_group_sender: Sender,
55 | }
56 |
57 | impl<'a> WgpuResourcesReadLock<'a> {
58 | pub fn refs(&'a self) -> WgpuResourceRefs<'a> {
59 | WgpuResourceRefs {
60 | buffers: &self.buffers,
61 | textures: &self.textures,
62 | swap_chain_frames: &self.swap_chain_frames,
63 | render_pipelines: &self.render_pipelines,
64 | bind_groups: &self.bind_groups,
65 | used_bind_group_sender: &self.used_bind_group_sender,
66 | }
67 | }
68 | }
69 |
70 | /// Stores read only references to WgpuResource collections. See WgpuResourcesReadLock docs for
71 | /// context on why this exists
72 | #[derive(Debug)]
73 | pub struct WgpuResourceRefs<'a> {
74 | pub buffers: &'a HashMap>,
75 | pub textures: &'a HashMap,
76 | pub swap_chain_frames: &'a HashMap,
77 | pub render_pipelines: &'a HashMap, wgpu::RenderPipeline>,
78 | pub bind_groups: &'a HashMap,
79 | pub used_bind_group_sender: &'a Sender,
80 | }
81 |
82 | #[derive(Default, Clone, Debug)]
83 | pub struct WgpuResources {
84 | pub buffer_infos: Arc>>,
85 | pub texture_descriptors: Arc>>,
86 | pub window_surfaces: Arc>>,
87 | pub window_swap_chains: Arc>>,
88 | pub swap_chain_frames: Arc>>,
89 | pub buffers: Arc>>>,
90 | pub texture_views: Arc>>,
91 | pub textures: Arc>>,
92 | pub samplers: Arc>>,
93 | pub shader_modules: Arc, wgpu::ShaderModule>>>,
94 | pub render_pipelines: Arc, wgpu::RenderPipeline>>>,
95 | pub bind_groups: Arc>>,
96 | pub bind_group_layouts: Arc>>,
97 | pub asset_resources: Arc>>,
98 | pub bind_group_counter: BindGroupCounter,
99 | }
100 |
101 | impl WgpuResources {
102 | pub fn read(&self) -> WgpuResourcesReadLock {
103 | WgpuResourcesReadLock {
104 | buffers: self.buffers.read(),
105 | textures: self.texture_views.read(),
106 | swap_chain_frames: self.swap_chain_frames.read(),
107 | render_pipelines: self.render_pipelines.read(),
108 | bind_groups: self.bind_groups.read(),
109 | used_bind_group_sender: self.bind_group_counter.used_bind_group_sender.clone(),
110 | }
111 | }
112 |
113 | pub fn has_bind_group(
114 | &self,
115 | bind_group_descriptor_id: BindGroupDescriptorId,
116 | bind_group_id: BindGroupId,
117 | ) -> bool {
118 | if let Some(bind_group_info) = self.bind_groups.read().get(&bind_group_descriptor_id) {
119 | bind_group_info.bind_groups.get(&bind_group_id).is_some()
120 | } else {
121 | false
122 | }
123 | }
124 |
125 | pub fn remove_stale_bind_groups(&self) {
126 | let mut bind_groups = self.bind_groups.write();
127 | self.bind_group_counter
128 | .remove_stale_bind_groups(&mut bind_groups);
129 | }
130 | }
131 |
132 | #[derive(Clone, Debug)]
133 | pub struct BindGroupCounter {
134 | pub used_bind_group_sender: Sender,
135 | pub used_bind_group_receiver: Receiver,
136 | pub bind_group_usage_counts: Arc>>,
137 | }
138 |
139 | impl BindGroupCounter {
140 | pub fn remove_stale_bind_groups(
141 | &self,
142 | bind_groups: &mut HashMap,
143 | ) {
144 | let mut bind_group_usage_counts = self.bind_group_usage_counts.write();
145 | loop {
146 | let bind_group = match self.used_bind_group_receiver.try_recv() {
147 | Ok(bind_group) => bind_group,
148 | Err(TryRecvError::Empty) => break,
149 | Err(TryRecvError::Disconnected) => panic!("used bind group channel disconnected"),
150 | };
151 |
152 | let count = bind_group_usage_counts.entry(bind_group).or_insert(0);
153 | // free every two frames
154 | *count = 2;
155 | }
156 |
157 | for info in bind_groups.values_mut() {
158 | info.bind_groups.retain(|id, _| {
159 | let retain = {
160 | // if a value hasn't been counted yet, give it two frames of leeway
161 | let count = bind_group_usage_counts.entry(*id).or_insert(2);
162 | *count -= 1;
163 | *count > 0
164 | };
165 | if !retain {
166 | bind_group_usage_counts.remove(&id);
167 | }
168 |
169 | retain
170 | })
171 | }
172 | }
173 | }
174 |
175 | impl Default for BindGroupCounter {
176 | fn default() -> Self {
177 | let (send, recv) = crossbeam_channel::unbounded();
178 | BindGroupCounter {
179 | used_bind_group_sender: send,
180 | used_bind_group_receiver: recv,
181 | bind_group_usage_counts: Default::default(),
182 | }
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/xsecurelock-saver/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "xsecurelock-saver"
3 | version = "0.1.0"
4 | authors = ["Zachary Stewart "]
5 | edition = "2018"
6 |
7 | [features]
8 | engine = ["bevy", "bevy_wgpu_xsecurelock"]
9 | simple = ["sfml"]
10 |
11 |
12 | [dependencies]
13 | bevy = { version = "0.5.0", optional = true }
14 | bevy_wgpu_xsecurelock = { path = "../third_party/bevy_wgpu_xsecurelock", optional = true }
15 | log = "0.4"
16 | sfml = { version = "0.16", optional = true }
17 | sigint = { path = "../sigint" }
18 |
--------------------------------------------------------------------------------
/xsecurelock-saver/src/engine.rs:
--------------------------------------------------------------------------------
1 | // Copyright 2018-2021 Google LLC
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //! A module providing an engine for game-like screensavers, using [Bevy](https://bevyengine.org).
16 | //! Provides [`XSecurelockSaverPlugins`] which replaces the Bevy [`DefaultPlugins`], and hacks the
17 | //! engine to use the window provided by XSecurelock instead of `winit` when running inside of
18 | //! XSecurelock. Outside of XSecurelock, functions like `DefaultPlugins`. You can plug this into an
19 | //! [`App`] like pretty much any other plugin.
20 | use std::env;
21 |
22 | use bevy::app::{Events, ManualEventReader, PluginGroupBuilder};
23 | use bevy::asset::{AssetPlugin, AssetServerSettings};
24 | use bevy::prelude::*;
25 | use bevy::wgpu::WgpuPlugin;
26 | use bevy::window::{CreateWindow, WindowCreated, WindowPlugin};
27 | use bevy::winit::WinitPlugin;
28 | use bevy_wgpu_xsecurelock::ExternalXWindow;
29 |
30 | /// A Bevy plugin for making the bevy app work as an X-Securelock screenaver using SFML rendering.
31 | #[derive(Debug)]
32 | pub struct XSecurelockSaverPlugins;
33 |
34 | impl PluginGroup for XSecurelockSaverPlugins {
35 | fn build(&mut self, plugins: &mut PluginGroupBuilder) {
36 | DefaultPlugins.build(plugins);
37 | plugins
38 | .disable::()
39 | .disable::()
40 | .add_before::(ConfigAssetsPlugin)
41 | .add_before::(ConfigWindowPlugin)
42 | .add(bevy_wgpu_xsecurelock::WgpuPlugin)
43 | .add(CreateWindowPlugin)
44 | .add(RunnerPlugin);
45 | }
46 | }
47 |
48 | const XSCREENSAVER_WINDOW: &str = "XSCREENSAVER_WINDOW";
49 |
50 | /// Adds an aset server config when running as a screensaver. Sets the asset location to the
51 | /// compile-time env variable `INSTALLED_SAVER_ASSET_PATH` when `XSCREENSAVER_WINDOW` is set.
52 | #[derive(Debug)]
53 | struct ConfigAssetsPlugin;
54 |
55 | impl Plugin for ConfigAssetsPlugin {
56 | fn build(&self, app: &mut AppBuilder) {
57 | const INSTALLED_ASSET_PATH: Option<&str> = option_env!("INSTALLED_SAVER_ASSET_PATH");
58 | if let Some(path) = INSTALLED_ASSET_PATH {
59 | if env::var_os(XSCREENSAVER_WINDOW).is_some() {
60 | app.insert_resource(AssetServerSettings {
61 | asset_folder: path.to_string(),
62 | });
63 | }
64 | }
65 | }
66 | }
67 |
68 | #[derive(Debug)]
69 | struct ConfigWindowPlugin;
70 |
71 | impl Plugin for ConfigWindowPlugin {
72 | fn build(&self, app: &mut AppBuilder) {
73 | // Get the ID of the window from the $XSCREENSAVER_WINDOW environment variable, and attach a ExternalXWindow if so.
74 | if let Ok(window_id_str) = env::var(XSCREENSAVER_WINDOW) {
75 | info!("Opening existing window");
76 | let handle = window_id_str.parse().expect("window id was not an integer");
77 | let external_window = ExternalXWindow::new(handle);
78 |
79 | app.insert_resource(external_window.bevy_window_descriptor());
80 | app.insert_resource(external_window);
81 | } else {
82 | info!("Using winit");
83 | app.add_plugin(WinitPlugin::default());
84 | }
85 | }
86 | }
87 |
88 | #[derive(Debug)]
89 | struct CreateWindowPlugin;
90 |
91 | impl Plugin for CreateWindowPlugin {
92 | fn build(&self, app: &mut AppBuilder) {
93 | if let Some(id) = app
94 | .world()
95 | .get_resource::()
96 | .map(|ew| ew.window_id)
97 | {
98 | info!("Checking for create window events to add ExternalXWindow");
99 | let world = app.world_mut().cell();
100 | let mut windows = world.get_resource_mut::().unwrap();
101 | let create_window_events = world.get_resource::>().unwrap();
102 | let mut window_created_events =
103 | world.get_resource_mut::>().unwrap();
104 | let mut added = false;
105 | for create_window_event in ManualEventReader::default().iter(&create_window_events) {
106 | if create_window_event.id == id {
107 | info!("Found matching event");
108 | let descriptor = world
109 | .get_resource::()
110 | .as_deref()
111 | .cloned()
112 | .unwrap();
113 | windows.add(Window::new(
114 | id,
115 | &descriptor,
116 | descriptor.width as u32,
117 | descriptor.height as u32,
118 | 1.0,
119 | None,
120 | ));
121 | window_created_events.send(WindowCreated {
122 | id: create_window_event.id,
123 | });
124 | added = true;
125 | } else {
126 | warn!(
127 | "Skipping non-xsecurlock window {:?}",
128 | create_window_event.id
129 | );
130 | }
131 | }
132 | if !added {
133 | warn!("Didn't find event for ExternalXWindow");
134 | let descriptor = world
135 | .get_resource::