refactored plugin loading + added scripting example

This commit is contained in:
Dmitry Stepanov
2022-02-23 13:40:47 +03:00
parent c5de06338d
commit 55ba1eeec5
14 changed files with 234 additions and 51 deletions
+1
View File
@@ -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
View File
@@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/examples/scripting" vcs="Git" />
</component>
</project>
+1
View File
@@ -23,6 +23,7 @@ members = [
"fyrox-ui",
"fyrox-resource",
"examples/wasm",
"examples/scripting",
"editor"
]
+2
View File
@@ -0,0 +1,2 @@
/target
Cargo.lock
+8
View File
@@ -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
+8
View File
@@ -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
View File
@@ -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>
+6
View File
@@ -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>
+13
View File
@@ -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 = "../../" }
+8
View File
@@ -0,0 +1,8 @@
(
plugins: [
(
name: "Scripting Plugin",
path: "../../target/debug/scripting.dll"
)
]
)
+122
View File
@@ -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
View File
@@ -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
View File
@@ -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> {
+8
View File
@@ -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>,