mirror of
https://github.com/FyroxEngine/Fyrox.git
synced 2026-10-12 02:02:20 +00:00
refactored plugin loading + added scripting example
This commit is contained in:
Generated
+1
@@ -26,6 +26,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/fyrox-sound/examples" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/fyrox-sound/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/fyrox-ui/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples/scripting/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/examples/wasm/target" />
|
||||
</content>
|
||||
|
||||
Generated
+1
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/examples/scripting" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -23,6 +23,7 @@ members = [
|
||||
"fyrox-ui",
|
||||
"fyrox-resource",
|
||||
"examples/wasm",
|
||||
"examples/scripting",
|
||||
"editor"
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/test_scripting.iml" filepath="$PROJECT_DIR$/.idea/test_scripting.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="CPP_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "test_scripting"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
name = "scripting"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
fyrox = { path = "../../" }
|
||||
@@ -0,0 +1,8 @@
|
||||
(
|
||||
plugins: [
|
||||
(
|
||||
name: "Scripting Plugin",
|
||||
path: "../../target/debug/scripting.dll"
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,122 @@
|
||||
use fyrox::core::algebra::{UnitQuaternion, Vector3};
|
||||
use fyrox::core::sstorage::ImmutableString;
|
||||
use fyrox::material::PropertyValue;
|
||||
use fyrox::scene::mesh::Mesh;
|
||||
use fyrox::{
|
||||
core::{
|
||||
color::Hsv,
|
||||
inspect::{Inspect, PropertyInfo},
|
||||
uuid::Uuid,
|
||||
visitor::prelude::*,
|
||||
},
|
||||
plugin::{Plugin, PluginContext},
|
||||
script::{ScriptContext, ScriptDefinition, ScriptDefinitionStorage, ScriptTrait},
|
||||
};
|
||||
use std::any::TypeId;
|
||||
use std::{str::FromStr, sync::Arc};
|
||||
|
||||
#[derive(Visit, Inspect, Default)]
|
||||
struct GamePlugin {
|
||||
#[visit(skip)]
|
||||
#[inspect(skip)]
|
||||
script_storage: Arc<ScriptDefinitionStorage>,
|
||||
}
|
||||
|
||||
impl GamePlugin {
|
||||
fn type_uuid() -> Uuid {
|
||||
Uuid::from_str("a9507fb2-0945-4fc1-91ce-115ae7c8a615").unwrap()
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
let mut script_storage = ScriptDefinitionStorage::new();
|
||||
|
||||
script_storage.add(ScriptDefinition {
|
||||
name: "TestScript".to_string(),
|
||||
type_uuid: TestScript::type_uuid(),
|
||||
constructor: Box::new(|| Box::new(TestScript::default())),
|
||||
});
|
||||
|
||||
Self {
|
||||
script_storage: Arc::new(script_storage),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Plugin for GamePlugin {
|
||||
fn on_init(&mut self, _engine: &mut PluginContext) {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
fn on_unload(&mut self, _context: &mut PluginContext) {}
|
||||
|
||||
fn update(&mut self, _context: &mut PluginContext) {}
|
||||
|
||||
fn script_definition_storage(&self) -> Arc<ScriptDefinitionStorage> {
|
||||
self.script_storage.clone()
|
||||
}
|
||||
|
||||
fn type_uuid(&self) -> Uuid {
|
||||
Self::type_uuid()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Visit, Inspect, Debug, Clone)]
|
||||
struct TestScript {
|
||||
foo: String,
|
||||
|
||||
hue: f32,
|
||||
}
|
||||
|
||||
impl Default for TestScript {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
foo: "Test String".to_string(),
|
||||
hue: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestScript {
|
||||
fn type_uuid() -> Uuid {
|
||||
Uuid::from_str("4aa165aa-011b-479f-bc10-b90b2c4b5060").unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ScriptTrait for TestScript {
|
||||
fn on_init(&mut self, _context: &mut ScriptContext) {}
|
||||
|
||||
fn on_update(&mut self, context: &mut ScriptContext) {
|
||||
let transform = context.node.local_transform_mut();
|
||||
let new_rotation = **transform.rotation()
|
||||
* UnitQuaternion::from_axis_angle(&Vector3::x_axis(), 1.0f32.to_radians());
|
||||
transform.set_rotation(new_rotation);
|
||||
|
||||
if let Some(mesh) = context.node.cast_mut::<Mesh>() {
|
||||
for surface in mesh.surfaces_mut() {
|
||||
surface
|
||||
.material()
|
||||
.lock()
|
||||
.set_property(
|
||||
&ImmutableString::new("diffuseColor"),
|
||||
PropertyValue::Color(Hsv::new(self.hue, 100.0, 100.0).into()),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
self.hue = (self.hue + 0.2) % 360.0;
|
||||
}
|
||||
|
||||
fn type_uuid(&self) -> Uuid {
|
||||
Self::type_uuid()
|
||||
}
|
||||
|
||||
fn plugin_uuid(&self) -> Uuid {
|
||||
GamePlugin::type_uuid()
|
||||
}
|
||||
}
|
||||
|
||||
// Script entry point.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn fyrox_main() -> Box<Box<dyn Plugin>> {
|
||||
Box::new(Box::new(GamePlugin::new()))
|
||||
}
|
||||
+1
-1
@@ -423,7 +423,7 @@ impl Engine {
|
||||
|
||||
/// Re-loads all available plugins.
|
||||
pub fn reload_plugins(&mut self) {
|
||||
self.plugins.rescan(&mut PluginContext {
|
||||
self.plugins.reload(&mut PluginContext {
|
||||
scenes: &mut self.scenes,
|
||||
ui: &mut self.user_interface,
|
||||
resource_manager: &self.resource_manager,
|
||||
|
||||
+44
-50
@@ -1,16 +1,29 @@
|
||||
use crate::{
|
||||
core::pool::{Handle, Pool},
|
||||
plugin::{DynamicPlugin, PluginContext},
|
||||
plugin::{DynamicPlugin, PluginContext, PluginDefinition},
|
||||
utils::{log::Log, watcher::FileSystemWatcher},
|
||||
};
|
||||
use notify::DebouncedEvent;
|
||||
use std::{collections::HashMap, ffi::OsString, path::PathBuf};
|
||||
use serde::Deserialize;
|
||||
use std::{error::Error, fs::File, path::Path};
|
||||
|
||||
pub struct PluginContainer {
|
||||
pub(crate) plugins: Pool<DynamicPlugin>,
|
||||
watcher: Option<FileSystemWatcher>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PluginContainerDefinition {
|
||||
plugins: Vec<PluginDefinition>,
|
||||
}
|
||||
|
||||
impl PluginContainerDefinition {
|
||||
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Box<dyn Error>> {
|
||||
let file = File::open(path)?;
|
||||
Ok(ron::de::from_reader(file)?)
|
||||
}
|
||||
}
|
||||
|
||||
fn unload_plugin(mut plugin: DynamicPlugin, context: &mut PluginContext) {
|
||||
// Destroy every script instance.
|
||||
for scene in context.scenes.iter_mut() {
|
||||
@@ -34,63 +47,44 @@ impl PluginContainer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rescan(&mut self, context: &mut PluginContext) {
|
||||
pub fn reload(&mut self, context: &mut PluginContext) {
|
||||
self.clear(context);
|
||||
|
||||
Log::info("Looking for plugins recursively from working directory...".to_owned());
|
||||
Log::info("Looking for `plugins.ron` in the working directory...".to_owned());
|
||||
|
||||
let mut libs = HashMap::<OsString, PathBuf>::new();
|
||||
for dir in walkdir::WalkDir::new(".").into_iter().flatten() {
|
||||
let path = dir.path();
|
||||
if let (Some(file_name), Some(extension)) = (path.file_name(), path.extension()) {
|
||||
if let Some(file_name_str) = path.file_stem().and_then(|s| s.to_str()) {
|
||||
if !file_name_str.ends_with("_plugin") {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
match PluginContainerDefinition::from_file("plugins.ron") {
|
||||
Ok(definition) => {
|
||||
for plugin_definition in definition.plugins {
|
||||
match DynamicPlugin::try_load(&plugin_definition.path) {
|
||||
Ok(mut plugin) => {
|
||||
plugin.on_init(context);
|
||||
let _ = self.plugins.spawn(plugin);
|
||||
|
||||
if extension == "dll" || extension == "dylib" || extension == "so" {
|
||||
if let Some(prev_candidate) = libs.get_mut(file_name) {
|
||||
if let (Ok(new_candidate_modified_time), Ok(prev_candidate_modified_time)) = (
|
||||
prev_candidate.metadata().and_then(|m| m.modified()),
|
||||
path.metadata().and_then(|m| m.modified()),
|
||||
) {
|
||||
if new_candidate_modified_time > prev_candidate_modified_time {
|
||||
*prev_candidate = path.to_path_buf();
|
||||
}
|
||||
Log::info(format!(
|
||||
"Plugin {} was loaded from {} successfully!",
|
||||
plugin_definition.name,
|
||||
plugin_definition.path.display()
|
||||
))
|
||||
}
|
||||
} else {
|
||||
libs.insert(file_name.to_os_string(), path.to_path_buf());
|
||||
Err(e) => Log::err(format!(
|
||||
"Unable to load {} plugin from {}. Reason: {:?}",
|
||||
plugin_definition.name,
|
||||
plugin_definition.path.display(),
|
||||
e
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
Log::info(format!(
|
||||
"{} plugins were loaded successfully!",
|
||||
self.plugins.alive_count()
|
||||
));
|
||||
}
|
||||
Err(e) => Log::err(format!(
|
||||
"Unable to load plugin definition container. Reason {:?}",
|
||||
e
|
||||
)),
|
||||
}
|
||||
|
||||
for path in libs.values() {
|
||||
match DynamicPlugin::try_load(path) {
|
||||
Ok(mut plugin) => {
|
||||
plugin.on_init(context);
|
||||
let _ = self.plugins.spawn(plugin);
|
||||
|
||||
Log::info(format!(
|
||||
"Plugin {} was loaded successfully!",
|
||||
path.display()
|
||||
))
|
||||
}
|
||||
Err(e) => Log::err(format!(
|
||||
"Unable to load plugin from {}. Reason: {:?}",
|
||||
path.display(),
|
||||
e
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
Log::info(format!(
|
||||
"{} plugins were loaded successfully!",
|
||||
self.plugins.alive_count()
|
||||
));
|
||||
}
|
||||
|
||||
pub fn add(&mut self, wrapper: DynamicPlugin) -> Handle<DynamicPlugin> {
|
||||
|
||||
@@ -7,9 +7,11 @@ use crate::{
|
||||
script::ScriptDefinitionStorage,
|
||||
};
|
||||
use libloading::{Library, Symbol};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
ffi::{OsStr, OsString},
|
||||
ops::{Deref, DerefMut},
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
@@ -37,6 +39,12 @@ pub trait Plugin: Visit + Inspect {
|
||||
|
||||
pub type EntryPoint = extern "C" fn() -> Box<Box<dyn Plugin>>;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PluginDefinition {
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
pub struct DynamicPlugin {
|
||||
entry: Box<dyn Plugin>,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user