mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-11 22:50:08 +00:00
split fyrox-template into lib + cli
- lib can be used in the project manager
This commit is contained in:
Generated
+1
@@ -41,6 +41,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/fyrox-impl/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/fyrox/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/project-manager/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/template-core/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/examples/wasm/target" />
|
||||
</content>
|
||||
|
||||
@@ -9,6 +9,7 @@ members = [
|
||||
"fyrox-animation",
|
||||
"editor",
|
||||
"editor-standalone",
|
||||
"template-core",
|
||||
"template",
|
||||
"fyrox-graph",
|
||||
"fyrox-math",
|
||||
|
||||
@@ -5,6 +5,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
fyrox = { path = "../fyrox" }
|
||||
fyrox-template-core = { version = "0.11.0", path = "../template-core" }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
ron = "0.8.1"
|
||||
open = "5.0.1"
|
||||
@@ -427,9 +427,10 @@ impl ProjectManager {
|
||||
))
|
||||
}
|
||||
|
||||
fn on_button_click(&mut self, button: Handle<UiNode>) {
|
||||
fn on_button_click(&mut self, button: Handle<UiNode>, ui: &mut UserInterface) {
|
||||
if button == self.create {
|
||||
// TODO: Create project.
|
||||
fyrox_template_core::init_project("test", "3d", "git", true).unwrap();
|
||||
self.refresh(ui);
|
||||
} else if button == self.import {
|
||||
// TODO: Import project.
|
||||
} else if button == self.download {
|
||||
@@ -450,7 +451,7 @@ impl ProjectManager {
|
||||
|
||||
fn handle_ui_message(&mut self, message: &UiMessage, ui: &mut UserInterface) {
|
||||
if let Some(ButtonMessage::Click) = message.data() {
|
||||
self.on_button_click(message.destination);
|
||||
self.on_button_click(message.destination, ui);
|
||||
} else if let Some(ListViewMessage::SelectionChanged(selection)) = message.data() {
|
||||
if message.destination() == self.projects
|
||||
&& message.direction() == MessageDirection::FromWidget
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "fyrox-template-core"
|
||||
version = "0.11.0"
|
||||
edition = "2021"
|
||||
authors = ["Dmitry Stepanov <[email protected]>"]
|
||||
license = "MIT"
|
||||
description = "Project template generator for Fyrox engine"
|
||||
keywords = ["fyrox", "game", "project"]
|
||||
categories = ["game-development"]
|
||||
include = ["/src/**/*", "/Cargo.toml", "/LICENSE", "/README.md"]
|
||||
homepage = "https://fyrox.rs"
|
||||
repository = "https://github.com/FyroxEngine/Fyrox"
|
||||
readme = "README.md"
|
||||
rust-version = "1.72"
|
||||
|
||||
[dependencies]
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
convert_case = "0.6.0"
|
||||
toml = "0.8.10"
|
||||
toml_edit = "0.22.6"
|
||||
regex = "1.7.3"
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Dmitry Stepanov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Fyrox Engine Template Generator
|
||||
|
||||
This tiny utility handles project and script generation for Fyrox Game Engine.
|
||||
|
||||
## Installation
|
||||
|
||||
Install it via `cargo install`:
|
||||
|
||||
```shell
|
||||
cargo install fyrox-template
|
||||
```
|
||||
|
||||
## Generating New Project
|
||||
|
||||
`fyrox-template init [--name <name> --style <style>]`
|
||||
|
||||
- `name` - a name of new project (default is `my_game`)
|
||||
- `style` - defines a default scene type, either `2d` or `3d` (default is `3d`)
|
||||
|
||||
It creates a workspace with three projects:
|
||||
|
||||
- Game - your game project (library)
|
||||
- Editor - the editor with your game attached as a plugin
|
||||
- Executor - the "runner" for your game.
|
||||
|
||||
It also populates each project with boilerplate code. The main purpose of the project is to reduce amount of time
|
||||
that is needed to set up a new project.
|
||||
|
||||
It will create a new folder with `<project_name>` and it will contain three projects, runnable only two of them:
|
||||
|
||||
- `cargo run --package editor --release` - to run your game inside the editor.
|
||||
- `cargo run --package executor --release` - to run your game as a standalone project. It will also produce final
|
||||
binary of your game, that can be shipped to a store.
|
||||
|
||||
### Tips
|
||||
|
||||
There is nothing special in generated project, so you can tweak them as you wish.
|
||||
|
||||
## Adding New Scripts
|
||||
|
||||
`fyrox-template script [--name <name>]`
|
||||
|
||||
- `name` - a name of your script (default is `MyScript`)
|
||||
|
||||
The tool is also capable to generate script skeleton for you, filling it with all required boilerplate. Generated scripts
|
||||
will be added to `game/src` folder, so you should run the tool from the root folder of your game (where the root Cargo.toml
|
||||
is located).
|
||||
|
||||
Do not forget to add the script to your module tree at required position, you probably will need some small tweaks
|
||||
to generated content, it can be easily automated by modern IDEs.
|
||||
@@ -0,0 +1,828 @@
|
||||
//! Fyrox Project Template Generator.
|
||||
|
||||
use convert_case::{Case, Casing};
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Display,
|
||||
fs::{create_dir_all, read_dir, remove_dir_all, File},
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
process::{exit, Command},
|
||||
};
|
||||
use toml_edit::{table, value, DocumentMut};
|
||||
use uuid::Uuid;
|
||||
|
||||
// Ideally, this should be take from respective Cargo.toml of the engine and the editor.
|
||||
// However, it does not seem to work with builds published to crates.io, because when
|
||||
// the template generator is published, it does not have these Cargo.toml's available
|
||||
// and to solve this we just hard code these values and pray for the best.
|
||||
const CURRENT_ENGINE_VERSION: &str = "0.34.0";
|
||||
const CURRENT_EDITOR_VERSION: &str = "0.21.0";
|
||||
const CURRENT_SCRIPTS_VERSION: &str = "0.3.0";
|
||||
|
||||
fn write_file<P: AsRef<Path>, S: AsRef<str>>(path: P, content: S) {
|
||||
let mut file = File::create(path.as_ref()).unwrap();
|
||||
file.write_all(content.as_ref().as_bytes())
|
||||
.unwrap_or_else(|x| {
|
||||
panic!(
|
||||
"Error happened while writing to file: {}.\nError:\n{}",
|
||||
path.as_ref().to_string_lossy(),
|
||||
x
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn write_file_binary<P: AsRef<Path>>(path: P, content: &[u8]) {
|
||||
let mut file = File::create(path.as_ref()).unwrap();
|
||||
file.write_all(content).unwrap_or_else(|x| {
|
||||
panic!(
|
||||
"Error happened while writing to file: {}.\nError:\n{}",
|
||||
path.as_ref().to_string_lossy(),
|
||||
x
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NameErrors {
|
||||
CargoReserved(String),
|
||||
Hyphen,
|
||||
StartsWithNumber,
|
||||
}
|
||||
|
||||
impl Display for NameErrors {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::CargoReserved(name) => write!(
|
||||
f,
|
||||
"The project name cannot be `{}` due to cargo's reserved keywords",
|
||||
name
|
||||
),
|
||||
Self::Hyphen => write!(f, "The project name cannot contain `-`"),
|
||||
Self::StartsWithNumber => write!(f, "The project name cannot start with a number"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_name(name: &str) -> Result<&str, NameErrors> {
|
||||
const RESERVED_NAMES: [&str; 53] = [
|
||||
"abstract", "alignof", "as", "become", "box", "break", "const", "continue", "crate", "do",
|
||||
"else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in", "let", "loop",
|
||||
"macro", "match", "mod", "move", "mut", "offsetof", "override", "priv", "proc", "pub",
|
||||
"pure", "ref", "return", "self", "sizeof", "static", "struct", "super", "test", "trait",
|
||||
"true", "type", "typeof", "try", "unsafe", "unsized", "use", "virtual", "where", "while",
|
||||
"yield",
|
||||
];
|
||||
if RESERVED_NAMES.contains(&name) {
|
||||
return Err(NameErrors::CargoReserved(name.to_string()));
|
||||
}
|
||||
if name.contains('-') {
|
||||
return Err(NameErrors::Hyphen);
|
||||
}
|
||||
if name.chars().next().unwrap_or(' ').is_ascii_digit() {
|
||||
return Err(NameErrors::StartsWithNumber);
|
||||
}
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
fn init_game(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--lib", "--vcs", "none"])
|
||||
.arg(base_path.join("game"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("game/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "{name}"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
fyrox = {{workspace = true}}
|
||||
|
||||
[features]
|
||||
default = ["fyrox/default"]
|
||||
dylib-engine = ["fyrox/dylib"]
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Write lib.rs
|
||||
write_file(
|
||||
base_path.join("game/src/lib.rs"),
|
||||
r#"//! Game project.
|
||||
use fyrox::{
|
||||
core::pool::Handle, core::visitor::prelude::*, core::reflect::prelude::*,
|
||||
event::Event,
|
||||
gui::message::UiMessage,
|
||||
plugin::{Plugin, PluginContext, PluginRegistrationContext},
|
||||
scene::Scene,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
// Re-export the engine.
|
||||
pub use fyrox;
|
||||
|
||||
#[derive(Default, Visit, Reflect, Debug)]
|
||||
pub struct Game {
|
||||
scene: Handle<Scene>,
|
||||
}
|
||||
|
||||
impl Plugin for Game {
|
||||
fn register(&self, _context: PluginRegistrationContext) {
|
||||
// Register your scripts here.
|
||||
}
|
||||
|
||||
fn init(&mut self, scene_path: Option<&str>, context: PluginContext) {
|
||||
context
|
||||
.async_scene_loader
|
||||
.request(scene_path.unwrap_or("data/scene.rgs"));
|
||||
}
|
||||
|
||||
fn on_deinit(&mut self, _context: PluginContext) {
|
||||
// Do a cleanup here.
|
||||
}
|
||||
|
||||
fn update(&mut self, _context: &mut PluginContext) {
|
||||
// Add your global update code here.
|
||||
}
|
||||
|
||||
fn on_os_event(
|
||||
&mut self,
|
||||
_event: &Event<()>,
|
||||
_context: PluginContext,
|
||||
) {
|
||||
// Do something on OS event here.
|
||||
}
|
||||
|
||||
fn on_ui_message(
|
||||
&mut self,
|
||||
_context: &mut PluginContext,
|
||||
_message: &UiMessage,
|
||||
) {
|
||||
// Handle UI events here.
|
||||
}
|
||||
|
||||
fn on_scene_begin_loading(&mut self, _path: &Path, ctx: &mut PluginContext) {
|
||||
if self.scene.is_some() {
|
||||
ctx.scenes.remove(self.scene);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_scene_loaded(
|
||||
&mut self,
|
||||
_path: &Path,
|
||||
scene: Handle<Scene>,
|
||||
_data: &[u8],
|
||||
_context: &mut PluginContext,
|
||||
) {
|
||||
self.scene = scene;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
fn init_executor(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--bin", "--vcs", "none"])
|
||||
.arg(base_path.join("executor"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("executor/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "executor"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
fyrox = {{ workspace = true }}
|
||||
{name} = {{ path = "../game", optional = true }}
|
||||
|
||||
[features]
|
||||
default = ["{name}"]
|
||||
dylib = ["fyrox/dylib"]
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Write main.rs
|
||||
write_file(
|
||||
base_path.join("executor/src/main.rs"),
|
||||
format!(
|
||||
r#"//! Executor with your game connected to it as a plugin.
|
||||
use fyrox::engine::executor::Executor;
|
||||
|
||||
fn main() {{
|
||||
let mut executor = Executor::new();
|
||||
|
||||
// Dynamic linking with hot reloading.
|
||||
#[cfg(feature = "dylib")]
|
||||
{{
|
||||
#[cfg(target_os = "windows")]
|
||||
let file_name = "game_dylib.dll";
|
||||
#[cfg(target_os = "linux")]
|
||||
let file_name = "libgame_dylib.so";
|
||||
#[cfg(target_os = "macos")]
|
||||
let file_name = "libgame_dylib.dylib";
|
||||
executor.add_dynamic_plugin(file_name, true, true).unwrap();
|
||||
}}
|
||||
|
||||
// Static linking.
|
||||
#[cfg(not(feature = "dylib"))]
|
||||
{{
|
||||
use {name}::Game;
|
||||
executor.add_plugin(Game::default());
|
||||
}}
|
||||
|
||||
executor.run()
|
||||
}}"#,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn init_wasm_executor(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--lib", "--vcs", "none"])
|
||||
.arg(base_path.join("executor-wasm"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("executor-wasm/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "executor-wasm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
fyrox = {{workspace = true}}
|
||||
{name} = {{ path = "../game" }}"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Write lib.rs
|
||||
write_file(
|
||||
base_path.join("executor-wasm/src/lib.rs"),
|
||||
format!(
|
||||
r#"//! Executor with your game connected to it as a plugin.
|
||||
use fyrox::engine::executor::Executor;
|
||||
use {name}::Game;
|
||||
use fyrox::core::wasm_bindgen::{{self, prelude::*}};
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {{
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn error(msg: String);
|
||||
|
||||
type Error;
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
fn new() -> Error;
|
||||
|
||||
#[wasm_bindgen(structural, method, getter)]
|
||||
fn stack(error: &Error) -> String;
|
||||
}}
|
||||
|
||||
fn custom_panic_hook(info: &std::panic::PanicInfo) {{
|
||||
let mut msg = info.to_string();
|
||||
msg.push_str("\n\nStack:\n\n");
|
||||
let e = Error::new();
|
||||
let stack = e.stack();
|
||||
msg.push_str(&stack);
|
||||
msg.push_str("\n\n");
|
||||
error(msg);
|
||||
}}
|
||||
|
||||
#[inline]
|
||||
pub fn set_panic_hook() {{
|
||||
use std::sync::Once;
|
||||
static SET_HOOK: Once = Once::new();
|
||||
SET_HOOK.call_once(|| {{
|
||||
std::panic::set_hook(Box::new(custom_panic_hook));
|
||||
}});
|
||||
}}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn main() {{
|
||||
set_panic_hook();
|
||||
let mut executor = Executor::new();
|
||||
executor.add_plugin(Game::default());
|
||||
executor.run()
|
||||
}}"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Write "entry" point stuff. This includes:
|
||||
//
|
||||
// - Index page with a "Start" button. The button is needed to solve sound issues in some browsers.
|
||||
// Some browsers (mostly Chrome) prevent sound from playing until user click on something on the
|
||||
// game page.
|
||||
// - Entry JavaScript code - basically a web launcher for your game.
|
||||
// - Styles - to make "Start" button to look decent.
|
||||
// - A readme file with build instructions.
|
||||
write_file_binary(
|
||||
base_path.join("executor-wasm/index.html"),
|
||||
include_bytes!("wasm/index.html"),
|
||||
);
|
||||
write_file_binary(
|
||||
base_path.join("executor-wasm/styles.css"),
|
||||
include_bytes!("wasm/styles.css"),
|
||||
);
|
||||
write_file_binary(
|
||||
base_path.join("executor-wasm/main.js"),
|
||||
include_bytes!("wasm/main.js"),
|
||||
);
|
||||
write_file_binary(
|
||||
base_path.join("executor-wasm/README.md"),
|
||||
include_bytes!("wasm/README.md"),
|
||||
);
|
||||
}
|
||||
|
||||
fn init_editor(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--bin", "--vcs", "none"])
|
||||
.arg(base_path.join("editor"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("editor/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "editor"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
fyrox = {{ workspace = true }}
|
||||
fyroxed_base = {{ workspace = true }}
|
||||
{name} = {{ path = "../game", optional = true }}
|
||||
|
||||
[features]
|
||||
default = ["{name}", "fyroxed_base/default"]
|
||||
dylib = ["fyroxed_base/dylib_engine"]
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
write_file(
|
||||
base_path.join("editor/src/main.rs"),
|
||||
format!(
|
||||
r#"//! Editor with your game connected to it as a plugin.
|
||||
use fyroxed_base::{{fyrox::event_loop::EventLoop, Editor, StartupData}};
|
||||
|
||||
fn main() {{
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let mut editor = Editor::new(
|
||||
Some(StartupData {{
|
||||
working_directory: Default::default(),
|
||||
scenes: vec!["data/scene.rgs".into()],
|
||||
}}),
|
||||
);
|
||||
|
||||
// Dynamic linking with hot reloading.
|
||||
#[cfg(feature = "dylib")]
|
||||
{{
|
||||
#[cfg(target_os = "windows")]
|
||||
let file_name = "game_dylib.dll";
|
||||
#[cfg(target_os = "linux")]
|
||||
let file_name = "libgame_dylib.so";
|
||||
#[cfg(target_os = "macos")]
|
||||
let file_name = "libgame_dylib.dylib";
|
||||
editor.add_dynamic_plugin(file_name, true, true).unwrap();
|
||||
}}
|
||||
|
||||
// Static linking.
|
||||
#[cfg(not(feature = "dylib"))]
|
||||
{{
|
||||
use {name}::Game;
|
||||
editor.add_game_plugin(Game::default());
|
||||
}}
|
||||
|
||||
editor.run(event_loop)
|
||||
}}
|
||||
"#,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn init_game_dylib(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--lib", "--vcs", "none"])
|
||||
.arg(base_path.join("game-dylib"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("game-dylib/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "game_dylib"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
{name} = {{ path = "../game", default-features = false }}
|
||||
|
||||
[features]
|
||||
default = ["{name}/default"]
|
||||
dylib-engine = ["{name}/dylib-engine"]
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Write lib.rs
|
||||
write_file(
|
||||
base_path.join("game-dylib/src/lib.rs"),
|
||||
format!(
|
||||
r#"//! Wrapper for hot-reloadable plugin.
|
||||
use {name}::{{fyrox::plugin::Plugin, Game}};
|
||||
|
||||
#[no_mangle]
|
||||
pub fn fyrox_plugin() -> Box<dyn Plugin> {{
|
||||
Box::new(Game::default())
|
||||
}}
|
||||
"#,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn init_android_executor(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--lib", "--vcs", "none"])
|
||||
.arg(base_path.join("executor-android"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("executor-android/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "executor-android"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata.android]
|
||||
# This folder is used as a temporary storage for assets. Project exporter will clone everything
|
||||
# from data folder to this folder and cargo-apk will create the apk with these assets.
|
||||
assets = "assets"
|
||||
strip = "strip"
|
||||
|
||||
[package.metadata.android.sdk]
|
||||
min_sdk_version = 26
|
||||
target_sdk_version = 30
|
||||
max_sdk_version = 29
|
||||
|
||||
[package.metadata.android.signing.release]
|
||||
path = "release.keystore"
|
||||
keystore_password = "fyrox-template"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
fyrox = {{ workspace = true }}
|
||||
{} = {{ path = "../game" }}"#,
|
||||
name,
|
||||
),
|
||||
);
|
||||
|
||||
// Write main.rs
|
||||
write_file(
|
||||
base_path.join("executor-android/src/lib.rs"),
|
||||
format!(
|
||||
r#"//! Android executor with your game connected to it as a plugin.
|
||||
use fyrox::{{
|
||||
core::io, engine::executor::Executor, event_loop::EventLoopBuilder,
|
||||
platform::android::EventLoopBuilderExtAndroid,
|
||||
}};
|
||||
use {name}::Game;
|
||||
|
||||
#[no_mangle]
|
||||
fn android_main(app: fyrox::platform::android::activity::AndroidApp) {{
|
||||
io::ANDROID_APP
|
||||
.set(app.clone())
|
||||
.expect("ANDROID_APP cannot be set twice.");
|
||||
let event_loop = EventLoopBuilder::new().with_android_app(app).build().unwrap();
|
||||
let mut executor = Executor::from_params(event_loop, Default::default());
|
||||
executor.add_plugin(Game::default());
|
||||
executor.run()
|
||||
}}"#,
|
||||
),
|
||||
);
|
||||
|
||||
write_file_binary(
|
||||
base_path.join("executor-android/README.md"),
|
||||
include_bytes!("android/README.md"),
|
||||
);
|
||||
write_file_binary(
|
||||
base_path.join("executor-android/release.keystore"),
|
||||
include_bytes!("android/release.keystore"),
|
||||
);
|
||||
create_dir_all(base_path.join("executor-android/assets")).unwrap();
|
||||
}
|
||||
|
||||
fn init_workspace(base_path: &Path, vcs: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--vcs", vcs])
|
||||
.arg(base_path)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
let src_path = base_path.join("src");
|
||||
if src_path.exists() {
|
||||
remove_dir_all(src_path).unwrap();
|
||||
}
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[workspace]
|
||||
members = ["editor", "executor", "executor-wasm", "executor-android", "game", "game-dylib"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies.fyrox]
|
||||
version = "{CURRENT_ENGINE_VERSION}"
|
||||
default-features = false
|
||||
[workspace.dependencies.fyroxed_base]
|
||||
version = "{CURRENT_EDITOR_VERSION}"
|
||||
default-features = false
|
||||
|
||||
# Separate build profiles for hot reloading. These profiles ensures that build artifacts for
|
||||
# hot reloading will be placed into their own folders and does not interfere with standard (static)
|
||||
# linking.
|
||||
[profile.dev-hot-reload]
|
||||
inherits = "dev"
|
||||
[profile.release-hot-reload]
|
||||
inherits = "release"
|
||||
|
||||
# Optimize the engine in debug builds, but leave project's code non-optimized.
|
||||
# By using this technique, you can still debug you code, but engine will be fully
|
||||
# optimized and debug builds won't be terribly slow. With this option, you can
|
||||
# compile your game in debug mode, which is much faster (at least x3), than release.
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
if vcs == "git" {
|
||||
// Write .gitignore
|
||||
write_file(
|
||||
base_path.join(".gitignore"),
|
||||
r#"
|
||||
/target
|
||||
*.log
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_data(base_path: &Path, style: &str) {
|
||||
let data_path = base_path.join("data");
|
||||
create_dir_all(&data_path).unwrap();
|
||||
|
||||
let scene_path = data_path.join("scene.rgs");
|
||||
match style {
|
||||
"2d" => write_file_binary(scene_path, include_bytes!("2d.rgs")),
|
||||
"3d" => write_file_binary(scene_path, include_bytes!("3d.rgs")),
|
||||
_ => println!("Unknown style: {}. Use either `2d` or `3d`", style),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_script(raw_name: &str) {
|
||||
let mut base_path = Path::new("game/src/");
|
||||
if !base_path.exists() {
|
||||
eprintln!("game/src directory does not exists! Fallback to root directory...");
|
||||
base_path = Path::new("");
|
||||
}
|
||||
|
||||
let script_file_stem = raw_name.to_case(Case::Snake);
|
||||
let script_name = raw_name.to_case(Case::UpperCamel);
|
||||
let file_name = base_path.join(script_file_stem.clone() + ".rs");
|
||||
|
||||
if file_name.exists() {
|
||||
panic!("Script {} already exists!", script_name);
|
||||
}
|
||||
|
||||
let script_uuid = Uuid::new_v4().to_string();
|
||||
|
||||
write_file(
|
||||
file_name,
|
||||
format!(
|
||||
r#"
|
||||
use fyrox::{{
|
||||
core::{{visitor::prelude::*, reflect::prelude::*, type_traits::prelude::*}},
|
||||
event::Event, script::{{ScriptContext, ScriptDeinitContext, ScriptTrait}},
|
||||
}};
|
||||
|
||||
#[derive(Visit, Reflect, Default, Debug, Clone, TypeUuidProvider, ComponentProvider)]
|
||||
#[type_uuid(id = "{id}")]
|
||||
#[visit(optional)]
|
||||
pub struct {name} {{
|
||||
// Add fields here.
|
||||
}}
|
||||
|
||||
impl ScriptTrait for {name} {{
|
||||
fn on_init(&mut self, context: &mut ScriptContext) {{
|
||||
// Put initialization logic here.
|
||||
}}
|
||||
|
||||
fn on_start(&mut self, context: &mut ScriptContext) {{
|
||||
// There should be a logic that depends on other scripts in scene.
|
||||
// It is called right after **all** scripts were initialized.
|
||||
}}
|
||||
|
||||
fn on_deinit(&mut self, context: &mut ScriptDeinitContext) {{
|
||||
// Put de-initialization logic here.
|
||||
}}
|
||||
|
||||
fn on_os_event(&mut self, event: &Event<()>, context: &mut ScriptContext) {{
|
||||
// Respond to OS events here.
|
||||
}}
|
||||
|
||||
fn on_update(&mut self, context: &mut ScriptContext) {{
|
||||
// Put object logic here.
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
name = script_name,
|
||||
id = script_uuid
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn init_project(name: &str, style: &str, vcs: &str, overwrite: bool) -> Result<(), String> {
|
||||
let name = check_name(name);
|
||||
let name = match name {
|
||||
Ok(s) => s,
|
||||
Err(name_error) => {
|
||||
println!("{}", name_error);
|
||||
return Err(name_error.to_string());
|
||||
}
|
||||
};
|
||||
|
||||
let base_path = Path::new(name);
|
||||
|
||||
// Check the path is empty / doesn't already exist (To prevent overriding)
|
||||
if !overwrite
|
||||
&& base_path.exists()
|
||||
&& read_dir(base_path)
|
||||
.expect("Failed to check if path is not empty")
|
||||
.next()
|
||||
.is_some()
|
||||
{
|
||||
return Err(format!(
|
||||
"Non-empty folder named {} already exists, provide --overwrite to create the project anyway",
|
||||
base_path.display()
|
||||
));
|
||||
}
|
||||
|
||||
init_workspace(base_path, vcs);
|
||||
init_data(base_path, style);
|
||||
init_game(base_path, name);
|
||||
init_game_dylib(base_path, name);
|
||||
init_editor(base_path, name);
|
||||
init_executor(base_path, name);
|
||||
init_wasm_executor(base_path, name);
|
||||
init_android_executor(base_path, name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn upgrade_project(version: &str, local: bool) {
|
||||
let semver_regex = Regex::new(include_str!("regex")).unwrap();
|
||||
|
||||
if version != "latest" && version != "nightly" && !semver_regex.is_match(version) {
|
||||
println!(
|
||||
"Invalid version: {version}. Please specify one of the following:\n\
|
||||
\tnightly - uses latest nightly version of the engine from GitHub directly.\
|
||||
\tlatest - uses latest stable version of the engine.\n\
|
||||
\tmajor.minor.patch - uses specific stable version from crates.io (0.30.0 for example).",
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Engine -> (Editor, Scripts) version mapping.
|
||||
let editor_versions = [
|
||||
(
|
||||
"0.34.0".to_string(),
|
||||
("0.21.0".to_string(), Some("0.3.0".to_string())),
|
||||
),
|
||||
(
|
||||
"0.33.0".to_string(),
|
||||
("0.20.0".to_string(), Some("0.2.0".to_string())),
|
||||
),
|
||||
(
|
||||
"0.32.0".to_string(),
|
||||
("0.19.0".to_string(), Some("0.1.0".to_string())),
|
||||
),
|
||||
("0.31.0".to_string(), ("0.18.0".to_string(), None)),
|
||||
("0.30.0".to_string(), ("0.17.0".to_string(), None)),
|
||||
("0.29.0".to_string(), ("0.16.0".to_string(), None)),
|
||||
("0.28.0".to_string(), ("0.15.0".to_string(), None)),
|
||||
("0.27.1".to_string(), ("0.14.1".to_string(), None)),
|
||||
("0.27.0".to_string(), ("0.14.0".to_string(), None)),
|
||||
("0.26.0".to_string(), ("0.13.0".to_string(), None)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
// Open workspace manifest.
|
||||
let workspace_manifest_path = "Cargo.toml";
|
||||
if let Ok(mut file) = File::open(workspace_manifest_path) {
|
||||
let mut toml = String::new();
|
||||
if file.read_to_string(&mut toml).is_ok() {
|
||||
drop(file);
|
||||
|
||||
if let Ok(mut document) = toml.parse::<DocumentMut>() {
|
||||
if let Some(workspace) =
|
||||
document.get_mut("workspace").and_then(|i| i.as_table_mut())
|
||||
{
|
||||
if let Some(dependencies) = workspace
|
||||
.get_mut("dependencies")
|
||||
.and_then(|i| i.as_table_mut())
|
||||
{
|
||||
if version == "latest" {
|
||||
if local {
|
||||
let mut engine_table = table();
|
||||
engine_table["path"] = value("../Fyrox/fyrox");
|
||||
dependencies["fyrox"] = engine_table;
|
||||
|
||||
let mut editor_table = table();
|
||||
editor_table["path"] = value("../Fyrox/editor");
|
||||
dependencies["fyroxed_base"] = editor_table;
|
||||
|
||||
if dependencies.contains_key("fyrox_scripts") {
|
||||
let mut scripts_table = table();
|
||||
scripts_table["path"] = value("../Fyrox/fyrox-scripts");
|
||||
dependencies["fyrox_scripts"] = scripts_table;
|
||||
}
|
||||
} else {
|
||||
dependencies["fyrox"] = value(CURRENT_ENGINE_VERSION);
|
||||
dependencies["fyroxed_base"] = value(CURRENT_EDITOR_VERSION);
|
||||
if dependencies.contains_key("fyrox_scripts") {
|
||||
dependencies["fyrox_scripts"] = value(CURRENT_SCRIPTS_VERSION);
|
||||
}
|
||||
}
|
||||
} else if version == "nightly" {
|
||||
let mut table = table();
|
||||
table["git"] = value("https://github.com/FyroxEngine/Fyrox");
|
||||
|
||||
dependencies["fyrox"] = table.clone();
|
||||
dependencies["fyroxed_base"] = table.clone();
|
||||
} else {
|
||||
dependencies["fyrox"] = value(version);
|
||||
if let Some((editor_version, scripts_version)) =
|
||||
editor_versions.get(version)
|
||||
{
|
||||
dependencies["fyroxed_base"] = value(editor_version);
|
||||
if let Some(scripts_version) = scripts_version {
|
||||
if dependencies.contains_key("fyrox_scripts") {
|
||||
dependencies["fyrox_scripts"] = value(scripts_version);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("WARNING: matching editor/scripts version not found!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut file = File::create(workspace_manifest_path).unwrap();
|
||||
file.write_all(document.to_string().as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::new("cargo").args(["update"]).output().unwrap();
|
||||
}
|
||||
+3
-7
@@ -4,7 +4,7 @@ version = "0.11.0"
|
||||
edition = "2021"
|
||||
authors = ["Dmitry Stepanov <[email protected]>"]
|
||||
license = "MIT"
|
||||
description = "Project template generator for Fyrox engine"
|
||||
description = "Command line interface for project template generator for Fyrox engine"
|
||||
keywords = ["fyrox", "game", "project"]
|
||||
categories = ["game-development"]
|
||||
include = ["/src/**/*", "/Cargo.toml", "/LICENSE", "/README.md"]
|
||||
@@ -14,9 +14,5 @@ readme = "README.md"
|
||||
rust-version = "1.72"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
convert_case = "0.6.0"
|
||||
toml = "0.8.10"
|
||||
toml_edit = "0.22.6"
|
||||
regex = "1.7.3"
|
||||
fyrox-template-core = { version = "0.11.0", path = "../template-core" }
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
+8
-831
@@ -1,18 +1,6 @@
|
||||
//! Fyrox Project Template Generator.
|
||||
//! Fyrox Project Template Generator command line interface.
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use convert_case::{Case, Casing};
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Display,
|
||||
fs::{create_dir_all, read_dir, remove_dir_all, File},
|
||||
io::{Read, Write},
|
||||
path::Path,
|
||||
process::{exit, Command},
|
||||
};
|
||||
use toml_edit::{table, value, DocumentMut};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about, long_about = None)]
|
||||
@@ -57,679 +45,6 @@ enum Commands {
|
||||
},
|
||||
}
|
||||
|
||||
// Ideally, this should be take from respective Cargo.toml of the engine and the editor.
|
||||
// However, it does not seem to work with builds published to crates.io, because when
|
||||
// the template generator is published, it does not have these Cargo.toml's available
|
||||
// and to solve this we just hard code these values and pray for the best.
|
||||
const CURRENT_ENGINE_VERSION: &str = "0.34.0";
|
||||
const CURRENT_EDITOR_VERSION: &str = "0.21.0";
|
||||
const CURRENT_SCRIPTS_VERSION: &str = "0.3.0";
|
||||
|
||||
fn write_file<P: AsRef<Path>, S: AsRef<str>>(path: P, content: S) {
|
||||
let mut file = File::create(path.as_ref()).unwrap();
|
||||
file.write_all(content.as_ref().as_bytes())
|
||||
.unwrap_or_else(|x| {
|
||||
panic!(
|
||||
"Error happened while writing to file: {}.\nError:\n{}",
|
||||
path.as_ref().to_string_lossy(),
|
||||
x
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn write_file_binary<P: AsRef<Path>>(path: P, content: &[u8]) {
|
||||
let mut file = File::create(path.as_ref()).unwrap();
|
||||
file.write_all(content).unwrap_or_else(|x| {
|
||||
panic!(
|
||||
"Error happened while writing to file: {}.\nError:\n{}",
|
||||
path.as_ref().to_string_lossy(),
|
||||
x
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum NameErrors {
|
||||
CargoReserved(String),
|
||||
Hyphen,
|
||||
StartsWithNumber,
|
||||
}
|
||||
|
||||
impl Display for NameErrors {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::CargoReserved(name) => write!(
|
||||
f,
|
||||
"The project name cannot be `{}` due to cargo's reserved keywords",
|
||||
name
|
||||
),
|
||||
Self::Hyphen => write!(f, "The project name cannot contain `-`"),
|
||||
Self::StartsWithNumber => write!(f, "The project name cannot start with a number"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_name(name: &str) -> Result<&str, NameErrors> {
|
||||
const RESERVED_NAMES: [&str; 53] = [
|
||||
"abstract", "alignof", "as", "become", "box", "break", "const", "continue", "crate", "do",
|
||||
"else", "enum", "extern", "false", "final", "fn", "for", "if", "impl", "in", "let", "loop",
|
||||
"macro", "match", "mod", "move", "mut", "offsetof", "override", "priv", "proc", "pub",
|
||||
"pure", "ref", "return", "self", "sizeof", "static", "struct", "super", "test", "trait",
|
||||
"true", "type", "typeof", "try", "unsafe", "unsized", "use", "virtual", "where", "while",
|
||||
"yield",
|
||||
];
|
||||
if RESERVED_NAMES.contains(&name) {
|
||||
return Err(NameErrors::CargoReserved(name.to_string()));
|
||||
}
|
||||
if name.contains('-') {
|
||||
return Err(NameErrors::Hyphen);
|
||||
}
|
||||
if name.chars().next().unwrap_or(' ').is_ascii_digit() {
|
||||
return Err(NameErrors::StartsWithNumber);
|
||||
}
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
fn init_game(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--lib", "--vcs", "none"])
|
||||
.arg(base_path.join("game"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("game/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "{name}"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
fyrox = {{workspace = true}}
|
||||
|
||||
[features]
|
||||
default = ["fyrox/default"]
|
||||
dylib-engine = ["fyrox/dylib"]
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Write lib.rs
|
||||
write_file(
|
||||
base_path.join("game/src/lib.rs"),
|
||||
r#"//! Game project.
|
||||
use fyrox::{
|
||||
core::pool::Handle, core::visitor::prelude::*, core::reflect::prelude::*,
|
||||
event::Event,
|
||||
gui::message::UiMessage,
|
||||
plugin::{Plugin, PluginContext, PluginRegistrationContext},
|
||||
scene::Scene,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
// Re-export the engine.
|
||||
pub use fyrox;
|
||||
|
||||
#[derive(Default, Visit, Reflect, Debug)]
|
||||
pub struct Game {
|
||||
scene: Handle<Scene>,
|
||||
}
|
||||
|
||||
impl Plugin for Game {
|
||||
fn register(&self, _context: PluginRegistrationContext) {
|
||||
// Register your scripts here.
|
||||
}
|
||||
|
||||
fn init(&mut self, scene_path: Option<&str>, context: PluginContext) {
|
||||
context
|
||||
.async_scene_loader
|
||||
.request(scene_path.unwrap_or("data/scene.rgs"));
|
||||
}
|
||||
|
||||
fn on_deinit(&mut self, _context: PluginContext) {
|
||||
// Do a cleanup here.
|
||||
}
|
||||
|
||||
fn update(&mut self, _context: &mut PluginContext) {
|
||||
// Add your global update code here.
|
||||
}
|
||||
|
||||
fn on_os_event(
|
||||
&mut self,
|
||||
_event: &Event<()>,
|
||||
_context: PluginContext,
|
||||
) {
|
||||
// Do something on OS event here.
|
||||
}
|
||||
|
||||
fn on_ui_message(
|
||||
&mut self,
|
||||
_context: &mut PluginContext,
|
||||
_message: &UiMessage,
|
||||
) {
|
||||
// Handle UI events here.
|
||||
}
|
||||
|
||||
fn on_scene_begin_loading(&mut self, _path: &Path, ctx: &mut PluginContext) {
|
||||
if self.scene.is_some() {
|
||||
ctx.scenes.remove(self.scene);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_scene_loaded(
|
||||
&mut self,
|
||||
_path: &Path,
|
||||
scene: Handle<Scene>,
|
||||
_data: &[u8],
|
||||
_context: &mut PluginContext,
|
||||
) {
|
||||
self.scene = scene;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
fn init_executor(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--bin", "--vcs", "none"])
|
||||
.arg(base_path.join("executor"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("executor/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "executor"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
fyrox = {{ workspace = true }}
|
||||
{name} = {{ path = "../game", optional = true }}
|
||||
|
||||
[features]
|
||||
default = ["{name}"]
|
||||
dylib = ["fyrox/dylib"]
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Write main.rs
|
||||
write_file(
|
||||
base_path.join("executor/src/main.rs"),
|
||||
format!(
|
||||
r#"//! Executor with your game connected to it as a plugin.
|
||||
use fyrox::engine::executor::Executor;
|
||||
|
||||
fn main() {{
|
||||
let mut executor = Executor::new();
|
||||
|
||||
// Dynamic linking with hot reloading.
|
||||
#[cfg(feature = "dylib")]
|
||||
{{
|
||||
#[cfg(target_os = "windows")]
|
||||
let file_name = "game_dylib.dll";
|
||||
#[cfg(target_os = "linux")]
|
||||
let file_name = "libgame_dylib.so";
|
||||
#[cfg(target_os = "macos")]
|
||||
let file_name = "libgame_dylib.dylib";
|
||||
executor.add_dynamic_plugin(file_name, true, true).unwrap();
|
||||
}}
|
||||
|
||||
// Static linking.
|
||||
#[cfg(not(feature = "dylib"))]
|
||||
{{
|
||||
use {name}::Game;
|
||||
executor.add_plugin(Game::default());
|
||||
}}
|
||||
|
||||
executor.run()
|
||||
}}"#,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn init_wasm_executor(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--lib", "--vcs", "none"])
|
||||
.arg(base_path.join("executor-wasm"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("executor-wasm/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "executor-wasm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
fyrox = {{workspace = true}}
|
||||
{name} = {{ path = "../game" }}"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Write lib.rs
|
||||
write_file(
|
||||
base_path.join("executor-wasm/src/lib.rs"),
|
||||
format!(
|
||||
r#"//! Executor with your game connected to it as a plugin.
|
||||
use fyrox::engine::executor::Executor;
|
||||
use {name}::Game;
|
||||
use fyrox::core::wasm_bindgen::{{self, prelude::*}};
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {{
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn error(msg: String);
|
||||
|
||||
type Error;
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
fn new() -> Error;
|
||||
|
||||
#[wasm_bindgen(structural, method, getter)]
|
||||
fn stack(error: &Error) -> String;
|
||||
}}
|
||||
|
||||
fn custom_panic_hook(info: &std::panic::PanicInfo) {{
|
||||
let mut msg = info.to_string();
|
||||
msg.push_str("\n\nStack:\n\n");
|
||||
let e = Error::new();
|
||||
let stack = e.stack();
|
||||
msg.push_str(&stack);
|
||||
msg.push_str("\n\n");
|
||||
error(msg);
|
||||
}}
|
||||
|
||||
#[inline]
|
||||
pub fn set_panic_hook() {{
|
||||
use std::sync::Once;
|
||||
static SET_HOOK: Once = Once::new();
|
||||
SET_HOOK.call_once(|| {{
|
||||
std::panic::set_hook(Box::new(custom_panic_hook));
|
||||
}});
|
||||
}}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn main() {{
|
||||
set_panic_hook();
|
||||
let mut executor = Executor::new();
|
||||
executor.add_plugin(Game::default());
|
||||
executor.run()
|
||||
}}"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Write "entry" point stuff. This includes:
|
||||
//
|
||||
// - Index page with a "Start" button. The button is needed to solve sound issues in some browsers.
|
||||
// Some browsers (mostly Chrome) prevent sound from playing until user click on something on the
|
||||
// game page.
|
||||
// - Entry JavaScript code - basically a web launcher for your game.
|
||||
// - Styles - to make "Start" button to look decent.
|
||||
// - A readme file with build instructions.
|
||||
write_file_binary(
|
||||
base_path.join("executor-wasm/index.html"),
|
||||
include_bytes!("wasm/index.html"),
|
||||
);
|
||||
write_file_binary(
|
||||
base_path.join("executor-wasm/styles.css"),
|
||||
include_bytes!("wasm/styles.css"),
|
||||
);
|
||||
write_file_binary(
|
||||
base_path.join("executor-wasm/main.js"),
|
||||
include_bytes!("wasm/main.js"),
|
||||
);
|
||||
write_file_binary(
|
||||
base_path.join("executor-wasm/README.md"),
|
||||
include_bytes!("wasm/README.md"),
|
||||
);
|
||||
}
|
||||
|
||||
fn init_editor(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--bin", "--vcs", "none"])
|
||||
.arg(base_path.join("editor"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("editor/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "editor"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
fyrox = {{ workspace = true }}
|
||||
fyroxed_base = {{ workspace = true }}
|
||||
{name} = {{ path = "../game", optional = true }}
|
||||
|
||||
[features]
|
||||
default = ["{name}", "fyroxed_base/default"]
|
||||
dylib = ["fyroxed_base/dylib_engine"]
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
write_file(
|
||||
base_path.join("editor/src/main.rs"),
|
||||
format!(
|
||||
r#"//! Editor with your game connected to it as a plugin.
|
||||
use fyroxed_base::{{fyrox::event_loop::EventLoop, Editor, StartupData}};
|
||||
|
||||
fn main() {{
|
||||
let event_loop = EventLoop::new().unwrap();
|
||||
let mut editor = Editor::new(
|
||||
Some(StartupData {{
|
||||
working_directory: Default::default(),
|
||||
scenes: vec!["data/scene.rgs".into()],
|
||||
}}),
|
||||
);
|
||||
|
||||
// Dynamic linking with hot reloading.
|
||||
#[cfg(feature = "dylib")]
|
||||
{{
|
||||
#[cfg(target_os = "windows")]
|
||||
let file_name = "game_dylib.dll";
|
||||
#[cfg(target_os = "linux")]
|
||||
let file_name = "libgame_dylib.so";
|
||||
#[cfg(target_os = "macos")]
|
||||
let file_name = "libgame_dylib.dylib";
|
||||
editor.add_dynamic_plugin(file_name, true, true).unwrap();
|
||||
}}
|
||||
|
||||
// Static linking.
|
||||
#[cfg(not(feature = "dylib"))]
|
||||
{{
|
||||
use {name}::Game;
|
||||
editor.add_game_plugin(Game::default());
|
||||
}}
|
||||
|
||||
editor.run(event_loop)
|
||||
}}
|
||||
"#,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn init_game_dylib(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--lib", "--vcs", "none"])
|
||||
.arg(base_path.join("game-dylib"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("game-dylib/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "game_dylib"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
{name} = {{ path = "../game", default-features = false }}
|
||||
|
||||
[features]
|
||||
default = ["{name}/default"]
|
||||
dylib-engine = ["{name}/dylib-engine"]
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
// Write lib.rs
|
||||
write_file(
|
||||
base_path.join("game-dylib/src/lib.rs"),
|
||||
format!(
|
||||
r#"//! Wrapper for hot-reloadable plugin.
|
||||
use {name}::{{fyrox::plugin::Plugin, Game}};
|
||||
|
||||
#[no_mangle]
|
||||
pub fn fyrox_plugin() -> Box<dyn Plugin> {{
|
||||
Box::new(Game::default())
|
||||
}}
|
||||
"#,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn init_android_executor(base_path: &Path, name: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--lib", "--vcs", "none"])
|
||||
.arg(base_path.join("executor-android"))
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("executor-android/Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[package]
|
||||
name = "executor-android"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata.android]
|
||||
# This folder is used as a temporary storage for assets. Project exporter will clone everything
|
||||
# from data folder to this folder and cargo-apk will create the apk with these assets.
|
||||
assets = "assets"
|
||||
strip = "strip"
|
||||
|
||||
[package.metadata.android.sdk]
|
||||
min_sdk_version = 26
|
||||
target_sdk_version = 30
|
||||
max_sdk_version = 29
|
||||
|
||||
[package.metadata.android.signing.release]
|
||||
path = "release.keystore"
|
||||
keystore_password = "fyrox-template"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
fyrox = {{ workspace = true }}
|
||||
{} = {{ path = "../game" }}"#,
|
||||
name,
|
||||
),
|
||||
);
|
||||
|
||||
// Write main.rs
|
||||
write_file(
|
||||
base_path.join("executor-android/src/lib.rs"),
|
||||
format!(
|
||||
r#"//! Android executor with your game connected to it as a plugin.
|
||||
use fyrox::{{
|
||||
core::io, engine::executor::Executor, event_loop::EventLoopBuilder,
|
||||
platform::android::EventLoopBuilderExtAndroid,
|
||||
}};
|
||||
use {name}::Game;
|
||||
|
||||
#[no_mangle]
|
||||
fn android_main(app: fyrox::platform::android::activity::AndroidApp) {{
|
||||
io::ANDROID_APP
|
||||
.set(app.clone())
|
||||
.expect("ANDROID_APP cannot be set twice.");
|
||||
let event_loop = EventLoopBuilder::new().with_android_app(app).build().unwrap();
|
||||
let mut executor = Executor::from_params(event_loop, Default::default());
|
||||
executor.add_plugin(Game::default());
|
||||
executor.run()
|
||||
}}"#,
|
||||
),
|
||||
);
|
||||
|
||||
write_file_binary(
|
||||
base_path.join("executor-android/README.md"),
|
||||
include_bytes!("android/README.md"),
|
||||
);
|
||||
write_file_binary(
|
||||
base_path.join("executor-android/release.keystore"),
|
||||
include_bytes!("android/release.keystore"),
|
||||
);
|
||||
create_dir_all(base_path.join("executor-android/assets")).unwrap();
|
||||
}
|
||||
|
||||
fn init_workspace(base_path: &Path, vcs: &str) {
|
||||
Command::new("cargo")
|
||||
.args(["init", "--vcs", vcs])
|
||||
.arg(base_path)
|
||||
.output()
|
||||
.unwrap();
|
||||
|
||||
let src_path = base_path.join("src");
|
||||
if src_path.exists() {
|
||||
remove_dir_all(src_path).unwrap();
|
||||
}
|
||||
|
||||
// Write Cargo.toml
|
||||
write_file(
|
||||
base_path.join("Cargo.toml"),
|
||||
format!(
|
||||
r#"
|
||||
[workspace]
|
||||
members = ["editor", "executor", "executor-wasm", "executor-android", "game", "game-dylib"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.dependencies.fyrox]
|
||||
version = "{CURRENT_ENGINE_VERSION}"
|
||||
default-features = false
|
||||
[workspace.dependencies.fyroxed_base]
|
||||
version = "{CURRENT_EDITOR_VERSION}"
|
||||
default-features = false
|
||||
|
||||
# Separate build profiles for hot reloading. These profiles ensures that build artifacts for
|
||||
# hot reloading will be placed into their own folders and does not interfere with standard (static)
|
||||
# linking.
|
||||
[profile.dev-hot-reload]
|
||||
inherits = "dev"
|
||||
[profile.release-hot-reload]
|
||||
inherits = "release"
|
||||
|
||||
# Optimize the engine in debug builds, but leave project's code non-optimized.
|
||||
# By using this technique, you can still debug you code, but engine will be fully
|
||||
# optimized and debug builds won't be terribly slow. With this option, you can
|
||||
# compile your game in debug mode, which is much faster (at least x3), than release.
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
"#,
|
||||
),
|
||||
);
|
||||
|
||||
if vcs == "git" {
|
||||
// Write .gitignore
|
||||
write_file(
|
||||
base_path.join(".gitignore"),
|
||||
r#"
|
||||
/target
|
||||
*.log
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_data(base_path: &Path, style: &str) {
|
||||
let data_path = base_path.join("data");
|
||||
create_dir_all(&data_path).unwrap();
|
||||
|
||||
let scene_path = data_path.join("scene.rgs");
|
||||
match style {
|
||||
"2d" => write_file_binary(scene_path, include_bytes!("2d.rgs")),
|
||||
"3d" => write_file_binary(scene_path, include_bytes!("3d.rgs")),
|
||||
_ => println!("Unknown style: {}. Use either `2d` or `3d`", style),
|
||||
}
|
||||
}
|
||||
|
||||
fn init_script(raw_name: &str) {
|
||||
let mut base_path = Path::new("game/src/");
|
||||
if !base_path.exists() {
|
||||
eprintln!("game/src directory does not exists! Fallback to root directory...");
|
||||
base_path = Path::new("");
|
||||
}
|
||||
|
||||
let script_file_stem = raw_name.to_case(Case::Snake);
|
||||
let script_name = raw_name.to_case(Case::UpperCamel);
|
||||
let file_name = base_path.join(script_file_stem.clone() + ".rs");
|
||||
|
||||
if file_name.exists() {
|
||||
panic!("Script {} already exists!", script_name);
|
||||
}
|
||||
|
||||
let script_uuid = Uuid::new_v4().to_string();
|
||||
|
||||
write_file(
|
||||
file_name,
|
||||
format!(
|
||||
r#"
|
||||
use fyrox::{{
|
||||
core::{{visitor::prelude::*, reflect::prelude::*, type_traits::prelude::*}},
|
||||
event::Event, script::{{ScriptContext, ScriptDeinitContext, ScriptTrait}},
|
||||
}};
|
||||
|
||||
#[derive(Visit, Reflect, Default, Debug, Clone, TypeUuidProvider, ComponentProvider)]
|
||||
#[type_uuid(id = "{id}")]
|
||||
#[visit(optional)]
|
||||
pub struct {name} {{
|
||||
// Add fields here.
|
||||
}}
|
||||
|
||||
impl ScriptTrait for {name} {{
|
||||
fn on_init(&mut self, context: &mut ScriptContext) {{
|
||||
// Put initialization logic here.
|
||||
}}
|
||||
|
||||
fn on_start(&mut self, context: &mut ScriptContext) {{
|
||||
// There should be a logic that depends on other scripts in scene.
|
||||
// It is called right after **all** scripts were initialized.
|
||||
}}
|
||||
|
||||
fn on_deinit(&mut self, context: &mut ScriptDeinitContext) {{
|
||||
// Put de-initialization logic here.
|
||||
}}
|
||||
|
||||
fn on_os_event(&mut self, event: &Event<()>, context: &mut ScriptContext) {{
|
||||
// Respond to OS events here.
|
||||
}}
|
||||
|
||||
fn on_update(&mut self, context: &mut ScriptContext) {{
|
||||
// Put object logic here.
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
name = script_name,
|
||||
id = script_uuid
|
||||
),
|
||||
);
|
||||
|
||||
println!(
|
||||
"Script {script_name} was added successfully! Do not forget to add it to your module tree by \
|
||||
adding:\n\tpub mod {script_file_stem};\nat either lib.rs or some other module.",
|
||||
);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Args = Args::parse();
|
||||
|
||||
@@ -740,40 +55,7 @@ fn main() {
|
||||
vcs,
|
||||
overwrite,
|
||||
} => {
|
||||
let name = check_name(&name);
|
||||
let name = match name {
|
||||
Ok(s) => s,
|
||||
Err(name_error) => {
|
||||
println!("{}", name_error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let base_path = Path::new(name);
|
||||
|
||||
// Check the path is empty / doesn't already exist (To prevent overriding)
|
||||
if !overwrite
|
||||
&& base_path.exists()
|
||||
&& read_dir(base_path)
|
||||
.expect("Failed to check if path is not empty")
|
||||
.next()
|
||||
.is_some()
|
||||
{
|
||||
println!(
|
||||
"Non-empty folder named {} already exists, provide --overwrite to create the project anyway",
|
||||
base_path.display()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
init_workspace(base_path, &vcs);
|
||||
init_data(base_path, &style);
|
||||
init_game(base_path, name);
|
||||
init_game_dylib(base_path, name);
|
||||
init_editor(base_path, name);
|
||||
init_executor(base_path, name);
|
||||
init_wasm_executor(base_path, name);
|
||||
init_android_executor(base_path, name);
|
||||
fyrox_template_core::init_project(&name, &style, &vcs, overwrite).unwrap();
|
||||
|
||||
println!("Project {} was generated successfully!", name);
|
||||
println!(
|
||||
@@ -790,119 +72,14 @@ fn main() {
|
||||
);
|
||||
}
|
||||
Commands::Script { name } => {
|
||||
init_script(&name);
|
||||
fyrox_template_core::init_script(&name);
|
||||
|
||||
println!(
|
||||
"Script {name} was added successfully! Do not forget to add it to your module tree!",
|
||||
);
|
||||
}
|
||||
Commands::Upgrade { version, local } => {
|
||||
let semver_regex = Regex::new(include_str!("regex")).unwrap();
|
||||
|
||||
if version != "latest" && version != "nightly" && !semver_regex.is_match(&version) {
|
||||
println!(
|
||||
"Invalid version: {version}. Please specify one of the following:\n\
|
||||
\tnightly - uses latest nightly version of the engine from GitHub directly.\
|
||||
\tlatest - uses latest stable version of the engine.\n\
|
||||
\tmajor.minor.patch - uses specific stable version from crates.io (0.30.0 for example).",
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Engine -> (Editor, Scripts) version mapping.
|
||||
let editor_versions = [
|
||||
(
|
||||
"0.34.0".to_string(),
|
||||
("0.21.0".to_string(), Some("0.3.0".to_string())),
|
||||
),
|
||||
(
|
||||
"0.33.0".to_string(),
|
||||
("0.20.0".to_string(), Some("0.2.0".to_string())),
|
||||
),
|
||||
(
|
||||
"0.32.0".to_string(),
|
||||
("0.19.0".to_string(), Some("0.1.0".to_string())),
|
||||
),
|
||||
("0.31.0".to_string(), ("0.18.0".to_string(), None)),
|
||||
("0.30.0".to_string(), ("0.17.0".to_string(), None)),
|
||||
("0.29.0".to_string(), ("0.16.0".to_string(), None)),
|
||||
("0.28.0".to_string(), ("0.15.0".to_string(), None)),
|
||||
("0.27.1".to_string(), ("0.14.1".to_string(), None)),
|
||||
("0.27.0".to_string(), ("0.14.0".to_string(), None)),
|
||||
("0.26.0".to_string(), ("0.13.0".to_string(), None)),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
// Open workspace manifest.
|
||||
let workspace_manifest_path = "Cargo.toml";
|
||||
if let Ok(mut file) = File::open(workspace_manifest_path) {
|
||||
let mut toml = String::new();
|
||||
if file.read_to_string(&mut toml).is_ok() {
|
||||
drop(file);
|
||||
|
||||
if let Ok(mut document) = toml.parse::<DocumentMut>() {
|
||||
if let Some(workspace) =
|
||||
document.get_mut("workspace").and_then(|i| i.as_table_mut())
|
||||
{
|
||||
if let Some(dependencies) = workspace
|
||||
.get_mut("dependencies")
|
||||
.and_then(|i| i.as_table_mut())
|
||||
{
|
||||
if version == "latest" {
|
||||
if local {
|
||||
let mut engine_table = table();
|
||||
engine_table["path"] = value("../Fyrox/fyrox");
|
||||
dependencies["fyrox"] = engine_table;
|
||||
|
||||
let mut editor_table = table();
|
||||
editor_table["path"] = value("../Fyrox/editor");
|
||||
dependencies["fyroxed_base"] = editor_table;
|
||||
|
||||
if dependencies.contains_key("fyrox_scripts") {
|
||||
let mut scripts_table = table();
|
||||
scripts_table["path"] = value("../Fyrox/fyrox-scripts");
|
||||
dependencies["fyrox_scripts"] = scripts_table;
|
||||
}
|
||||
} else {
|
||||
dependencies["fyrox"] = value(CURRENT_ENGINE_VERSION);
|
||||
dependencies["fyroxed_base"] =
|
||||
value(CURRENT_EDITOR_VERSION);
|
||||
if dependencies.contains_key("fyrox_scripts") {
|
||||
dependencies["fyrox_scripts"] =
|
||||
value(CURRENT_SCRIPTS_VERSION);
|
||||
}
|
||||
}
|
||||
} else if version == "nightly" {
|
||||
let mut table = table();
|
||||
table["git"] = value("https://github.com/FyroxEngine/Fyrox");
|
||||
|
||||
dependencies["fyrox"] = table.clone();
|
||||
dependencies["fyroxed_base"] = table.clone();
|
||||
} else {
|
||||
dependencies["fyrox"] = value(version.clone());
|
||||
if let Some((editor_version, scripts_version)) =
|
||||
editor_versions.get(&version)
|
||||
{
|
||||
dependencies["fyroxed_base"] = value(editor_version);
|
||||
if let Some(scripts_version) = scripts_version {
|
||||
if dependencies.contains_key("fyrox_scripts") {
|
||||
dependencies["fyrox_scripts"] =
|
||||
value(scripts_version);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!(
|
||||
"WARNING: matching editor/scripts version not found!"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut file = File::create(workspace_manifest_path).unwrap();
|
||||
file.write_all(document.to_string().as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::new("cargo").args(["update"]).output().unwrap();
|
||||
fyrox_template_core::upgrade_project(&version, local);
|
||||
|
||||
println!("Fyrox version was successfully set to '{}'!", version);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user