use std::collections::{HashMap, HashSet};
use bevy::{
DefaultPlugins,
app::{PluginGroupBuilder, ScheduleRunnerPlugin, TaskPoolPlugin},
diagnostic::FrameCountPlugin,
prelude::PluginGroup,
time::TimePlugin,
};
use pybevy_core::PyPlugin;
use pyo3::{exceptions::PyTypeError, prelude::*, types::PyType};
use super::{app::PyApp, plugin::PyPluginGroup, plugin_config::PluginConfigType};
use crate::assets::configured_asset_plugin;
fn default_window_plugin() -> bevy::window::WindowPlugin {
bevy::window::WindowPlugin {
primary_window: Some(bevy::window::Window {
title: pybevy_window::DEFAULT_APP_TITLE.into(),
name: Some("pybevy".into()),
..Default::default()
}),
..Default::default()
}
}
#[pyclass(name = "DefaultPlugins", extends = PyPluginGroup)]
pub struct PyDefaultPlugins;
#[pymethods]
impl PyDefaultPlugins {
#[new]
pub fn new() -> (Self, PyPluginGroup) {
(PyDefaultPlugins, PyPluginGroup)
}
pub fn set(&self, py: Python, plugin: Bound<'_, PyAny>) -> PyResult<Py<PyPluginGroupBuilder>> {
let source_type =
py.get_type::<PyDefaultPlugins>().as_ptr() as *const pyo3::ffi::PyTypeObject;
let (builder_struct, plugin_base) = PyPluginGroupBuilder::new();
let builder_with_source = builder_struct.with_source_type(source_type);
let builder = Py::new(py, (builder_with_source, plugin_base))?;
builder.borrow(py).set(py, plugin)
}
pub fn build(&self, py: Python) -> PyResult<Py<PyPluginGroupBuilder>> {
let source_type =
py.get_type::<PyDefaultPlugins>().as_ptr() as *const pyo3::ffi::PyTypeObject;
let (builder_struct, plugin_base) = PyPluginGroupBuilder::new();
let builder_with_source = builder_struct.with_source_type(source_type);
Py::new(py, (builder_with_source, plugin_base))
}
pub fn _apply_to_app(&self, app: Bound<'_, PyApp>) -> PyResult<()> {
app.borrow().with_bevy_app(|bevy_app| {
bevy_app.add_plugins(
DefaultPlugins
.set(configured_asset_plugin())
.set(default_window_plugin())
.disable::<bevy::log::LogPlugin>(),
);
bevy_app.add_plugins(crate::render::wgpu_error_handler::WgpuErrorHandlerPlugin);
bevy_app.add_observer(pybevy_scene::scene_instance_ready_bridge);
Ok(())
})
}
}
#[derive(Clone)]
enum PluginPosition {
End,
Before,
After,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct PluginTypeId(*const pyo3::ffi::PyTypeObject);
impl PluginTypeId {
pub(crate) fn as_ptr(self) -> *const pyo3::ffi::PyTypeObject {
self.0
}
}
unsafe impl Send for PluginTypeId {}
unsafe impl Sync for PluginTypeId {}
#[pyclass(name = "PluginGroupBuilder", extends = PyPluginGroup)]
pub struct PyPluginGroupBuilder {
configured_plugins: HashMap<PluginConfigType, Py<PyAny>>,
disabled_plugins: HashSet<PluginConfigType>,
added_plugins: Vec<(PluginPosition, Py<PyAny>)>,
pub(crate) source_type: Option<PluginTypeId>,
}
impl Clone for PyPluginGroupBuilder {
fn clone(&self) -> Self {
Python::attach(|py| PyPluginGroupBuilder {
configured_plugins: self
.configured_plugins
.iter()
.map(|(k, v)| (*k, v.clone_ref(py)))
.collect(),
disabled_plugins: self.disabled_plugins.clone(),
added_plugins: self
.added_plugins
.iter()
.map(|(pos, plugin)| (pos.clone(), plugin.clone_ref(py)))
.collect(),
source_type: self.source_type,
})
}
}
impl PyPluginGroupBuilder {
fn new() -> (Self, PyPluginGroup) {
(
PyPluginGroupBuilder {
configured_plugins: HashMap::new(),
disabled_plugins: HashSet::new(),
added_plugins: Vec::new(),
source_type: None,
},
PyPluginGroup,
)
}
fn with_source_type(mut self, source_type: *const pyo3::ffi::PyTypeObject) -> Self {
self.source_type = Some(PluginTypeId(source_type));
self
}
}
#[pymethods]
impl PyPluginGroupBuilder {
pub fn set(&self, py: Python, plugin: Bound<'_, PyAny>) -> PyResult<Py<Self>> {
if !plugin.is_instance_of::<PyPlugin>() {
return Err(PyTypeError::new_err(
"Argument to .set() must be a Plugin instance",
));
}
let plugin_type = plugin.get_type();
let config_type = PluginConfigType::from_py_type(py, &plugin_type)?;
let mut new_builder = self.clone();
new_builder
.configured_plugins
.insert(config_type, plugin.unbind());
Py::new(py, (new_builder, PyPluginGroup))
}
pub fn disable(&self, py: Python, plugin_type: Bound<'_, PyType>) -> PyResult<Py<Self>> {
let config_type = PluginConfigType::from_py_type(py, &plugin_type)?;
let mut new_builder = self.clone();
new_builder.disabled_plugins.insert(config_type);
Py::new(py, (new_builder, PyPluginGroup))
}
pub fn add(&self, py: Python, plugin: Bound<'_, PyAny>) -> PyResult<Py<Self>> {
if !plugin.is_instance_of::<PyPlugin>() {
return Err(PyTypeError::new_err(
"Argument to .add() must be a Plugin instance",
));
}
let mut new_builder = self.clone();
new_builder
.added_plugins
.push((PluginPosition::End, plugin.unbind()));
Py::new(py, (new_builder, PyPluginGroup))
}
pub fn add_before(
&self,
py: Python,
target: Bound<'_, PyType>,
plugin: Bound<'_, PyAny>,
) -> PyResult<Py<Self>> {
if !plugin.is_instance_of::<PyPlugin>() {
return Err(PyTypeError::new_err(
"Argument to .add_before() must be a Plugin instance",
));
}
let _target_type = PluginConfigType::from_py_type(py, &target)?;
let mut new_builder = self.clone();
new_builder
.added_plugins
.push((PluginPosition::Before, plugin.unbind()));
Py::new(py, (new_builder, PyPluginGroup))
}
pub fn add_after(
&self,
py: Python,
target: Bound<'_, PyType>,
plugin: Bound<'_, PyAny>,
) -> PyResult<Py<Self>> {
if !plugin.is_instance_of::<PyPlugin>() {
return Err(PyTypeError::new_err(
"Argument to .add_after() must be a Plugin instance",
));
}
let _target_type = PluginConfigType::from_py_type(py, &target)?;
let mut new_builder = self.clone();
new_builder
.added_plugins
.push((PluginPosition::After, plugin.unbind()));
Py::new(py, (new_builder, PyPluginGroup))
}
pub fn enable(&self, py: Python, plugin_type: Bound<'_, PyType>) -> PyResult<Py<Self>> {
let config_type = PluginConfigType::from_py_type(py, &plugin_type)?;
let mut new_builder = self.clone();
new_builder.disabled_plugins.remove(&config_type);
Py::new(py, (new_builder, PyPluginGroup))
}
pub fn build(&self, app: Bound<'_, PyApp>) -> PyResult<()> {
app.borrow().with_bevy_app(|bevy_app| {
self.insert_pre_plugin_resources(app.py(), bevy_app)?;
let builder = self.apply_to_bevy(app.py())?;
bevy_app.add_plugins(builder);
bevy_app.add_plugins(crate::render::wgpu_error_handler::WgpuErrorHandlerPlugin);
bevy_app.add_observer(pybevy_scene::scene_instance_ready_bridge);
Ok(())
})
}
}
impl PyPluginGroupBuilder {
fn insert_pre_plugin_resources(
&self,
py: Python,
bevy_app: &mut bevy::app::App,
) -> PyResult<()> {
if let Some(plugin_obj) = self.configured_plugins.get(&PluginConfigType::Winit) {
let winit_plugin: PyRef<pybevy_window::PyWinitPlugin> = plugin_obj.extract(py)?;
if let Some(ref settings) = winit_plugin.settings {
bevy_app.insert_resource(bevy::winit::WinitSettings::from(settings.clone()));
}
}
Ok(())
}
fn apply_to_bevy(&self, py: Python) -> PyResult<PluginGroupBuilder> {
let mut builder = DefaultPlugins
.set(configured_asset_plugin())
.set(default_window_plugin())
.disable::<bevy::log::LogPlugin>();
for (config_type, plugin_obj) in &self.configured_plugins {
if !self.disabled_plugins.contains(config_type) {
builder = apply_plugin_configuration(builder, config_type, plugin_obj, py)?;
}
}
for disabled_type in &self.disabled_plugins {
builder = disable_plugin(builder, disabled_type)?;
}
Ok(builder)
}
}
fn apply_plugin_configuration(
builder: PluginGroupBuilder,
config_type: &PluginConfigType,
plugin_obj: &Py<PyAny>,
py: Python,
) -> PyResult<PluginGroupBuilder> {
match config_type {
PluginConfigType::Window => {
let window_plugin: PyRef<pybevy_window::PyWindowPlugin> = plugin_obj.extract(py)?;
let bevy_plugin = bevy::window::WindowPlugin::try_from(&*window_plugin)?;
Ok(builder.set(bevy_plugin))
}
PluginConfigType::Winit => {
Ok(builder.set(bevy::winit::WinitPlugin::default()))
}
PluginConfigType::Render => {
let render_plugin: PyRef<pybevy_render::PyRenderPlugin> = plugin_obj.extract(py)?;
let mut wgpu_settings = bevy::render::settings::WgpuSettings::default();
if let Some(ref pp) = render_plugin.power_preference {
wgpu_settings.power_preference = (*pp).into();
}
let mut bevy_plugin = bevy::render::RenderPlugin {
render_creation: bevy::render::settings::RenderCreation::Automatic(wgpu_settings),
..Default::default()
};
if let Some(sync) = render_plugin.synchronous_pipeline_compilation {
bevy_plugin.synchronous_pipeline_compilation = sync;
}
Ok(builder.set(bevy_plugin))
}
_ => Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
"Plugin configuration not yet implemented: {:?}",
config_type
))),
}
}
fn disable_plugin(
builder: PluginGroupBuilder,
config_type: &PluginConfigType,
) -> PyResult<PluginGroupBuilder> {
match config_type {
PluginConfigType::Window => Ok(builder.disable::<bevy::window::WindowPlugin>()),
PluginConfigType::Audio => Ok(builder.disable::<bevy::audio::AudioPlugin>()),
PluginConfigType::Winit => Ok(builder.disable::<bevy::winit::WinitPlugin>()),
PluginConfigType::Render => Ok(builder.disable::<bevy::render::RenderPlugin>()),
PluginConfigType::Image => Ok(builder.disable::<bevy::image::ImagePlugin>()),
PluginConfigType::TaskPool => Ok(builder.disable::<TaskPoolPlugin>()),
}
}
#[pyclass(name = "MinimalPlugins", extends = PyPlugin, frozen)]
#[derive(Debug, Clone, Copy)]
pub struct PyMinimalPlugins;
#[pymethods]
impl PyMinimalPlugins {
#[new]
pub fn new() -> (Self, PyPlugin) {
(PyMinimalPlugins, PyPlugin)
}
pub fn build(&self, app: Bound<'_, PyApp>) -> PyResult<()> {
app.borrow().with_bevy_app(|bevy_app| {
bevy_app.add_plugins((
TaskPoolPlugin::default(),
FrameCountPlugin::default(),
TimePlugin::default(),
ScheduleRunnerPlugin::default(),
));
Ok(())
})
}
}