use std::{ops::Deref, path::PathBuf, sync::Arc};
use ::{
bevy_app::{App, Plugin, Startup},
bevy_ecs::{prelude::Resource, reflect::AppTypeRegistry, system::Res, world::World},
};
use bevy_log::{error, info};
use bevy_mod_scripting_bindings::{
DummyScriptFunctionRegistry, IntoNamespace,
function::{namespace::Namespace, script_function::AppScriptFunctionRegistry},
globals::AppScriptGlobalsRegistry,
into_through_type_info,
};
use ladfile::{LadFieldOrVariableKind, LadFilePlugin, default_importance};
use crate::LadFileBuilder;
#[derive(Default, Clone)]
pub struct ScriptingFilesGenerationPlugin(LadFileSettingsArc);
#[derive(Resource, Default, Clone)]
pub struct LadFileSettingsArc(pub Arc<LadFileSettings>);
impl Deref for LadFileSettingsArc {
type Target = LadFileSettings;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct LadFileSettings {
pub enabled: bool,
pub ladfile_filename: Option<PathBuf>,
pub output_directory: PathBuf,
pub description: &'static str,
pub exclude_types_containing_unregistered: bool,
pub pretty: bool,
pub processors: Vec<Box<dyn LadFilePlugin + Send + Sync + 'static>>,
}
impl Default for LadFileSettings {
fn default() -> Self {
Self {
enabled: true,
ladfile_filename: None,
output_directory: PathBuf::from("."),
description: "",
pretty: true,
exclude_types_containing_unregistered: true,
processors: ScriptingFilesGenerationPlugin::default_processors(),
}
}
}
impl ScriptingFilesGenerationPlugin {
#[allow(unused_mut, clippy::vec_init_then_push)]
pub fn default_processors() -> Vec<Box<dyn LadFilePlugin + Send + Sync + 'static>> {
let mut processors = Vec::default();
#[cfg(feature = "lua_language_server_files")]
processors.push(Box::new(
lua_language_server_lad_backend::LuaLanguageServerLadPlugin::default(),
) as Box<dyn LadFilePlugin + Send + Sync + 'static>);
processors
}
pub fn new(
enabled: bool,
output_directory: PathBuf,
ladfile_filename: Option<PathBuf>,
description: &'static str,
exclude_types_containing_unregistered: bool,
pretty: bool,
) -> Self {
Self(LadFileSettingsArc(Arc::new(LadFileSettings {
enabled,
output_directory,
ladfile_filename,
description,
pretty,
exclude_types_containing_unregistered,
processors: Self::default_processors(),
})))
}
}
pub fn generate_lad_file(
type_registry: &AppTypeRegistry,
function_registry: &AppScriptFunctionRegistry,
dummy_function_registry: &DummyScriptFunctionRegistry,
global_registry: &AppScriptGlobalsRegistry,
settings: &LadFileSettings,
) {
let type_registry = type_registry.read();
let function_registry = function_registry.read();
let dummy_function_registry = dummy_function_registry.0.read();
let global_registry = global_registry.read();
let mut builder = LadFileBuilder::new(&type_registry);
builder
.set_description(settings.description)
.set_exclude_including_unregistered(settings.exclude_types_containing_unregistered)
.set_sorted(true);
builder.add_nonreflect_type::<World>(
Some("bevy_ecs"),
r#"The ECS world containing all Components, Resources and Systems. Main point of interaction with a Bevy App."#.trim(),
);
for (_, function) in function_registry
.iter_namespace(World::into_namespace())
.chain(dummy_function_registry.iter_namespace(World::into_namespace()))
{
builder.add_function_info(&function.info);
}
builder.set_insignificance(
std::any::TypeId::of::<World>(),
(default_importance() / 2) - 1,
);
for registration in type_registry.iter() {
let type_info = registration.type_info();
if type_info.type_path_table().ident().is_none() {
continue;
}
let through_type_info = into_through_type_info(type_info);
builder.add_through_type_info(&through_type_info);
for (_, function) in function_registry
.iter_namespace(Namespace::OnType(type_info.type_id()))
.chain(dummy_function_registry.iter_namespace(Namespace::OnType(type_info.type_id())))
{
builder.add_function_info(&function.info);
}
}
for (_, function) in function_registry
.iter_namespace(Namespace::Global)
.chain(dummy_function_registry.iter_namespace(Namespace::Global))
{
builder.add_function_info(&function.info);
}
for (key, global) in global_registry.iter() {
let type_info = global.type_information.clone();
builder.add_instance_dynamic(key.to_string(), global.maker.is_none(), type_info);
}
for (key, global) in global_registry.iter_dummies() {
let kind = if let Some(type_info) = &global.type_information {
builder.add_through_type_info(type_info);
builder.lad_type_kind_from_through_type(type_info)
} else {
LadFieldOrVariableKind::Val(builder.lad_id_from_type_id(global.type_id))
};
builder.add_instance_manually(key.to_string(), false, kind);
}
let file = builder.build();
let directory = &settings.output_directory;
for processor in settings.processors.iter() {
bevy_log::info!("Running ladfile processor: '{}'", processor.name());
if let Err(e) = processor.run(&file, directory) {
bevy_log::error!("Error in running ladfile processor: {e:#}")
}
}
if let Some(filename) = &settings.ladfile_filename {
let path = directory.join(filename);
let file = match ladfile::serialize_lad_file(&file, settings.pretty) {
Ok(file) => file,
Err(e) => {
error!("Error serializing LAD file: {}", e);
return;
}
};
match std::fs::write(&path, file) {
Ok(_) => {
info!("Successfully generated LAD file at {:?}", path);
}
Err(e) => {
error!("Error saving LAD file to {:?}: {}", path, e);
}
}
}
}
fn generate_lad_file_system(
type_registry: Res<AppTypeRegistry>,
function_registry: Res<AppScriptFunctionRegistry>,
dummy_function_registry: Res<DummyScriptFunctionRegistry>,
global_registry: Res<AppScriptGlobalsRegistry>,
settings: Res<LadFileSettingsArc>,
) {
generate_lad_file(
&type_registry,
&function_registry,
&dummy_function_registry,
&global_registry,
&settings.0,
);
}
impl Plugin for ScriptingFilesGenerationPlugin {
fn build(&self, app: &mut App) {
if self.0.0.enabled {
app.insert_resource(self.0.clone());
app.add_systems(Startup, generate_lad_file_system);
}
}
}