@ -5,24 +5,13 @@
// * Copy assets into local folder in leue of importing them
// * Check portability by moving binary + folder to new location
use std ::{
fs ::{ DirBuilder , File } ,
io ::Write ,
time ::Duration ,
} ;
use std ::time ::Duration ;
use bevy ::{
asset ::{ Asset , ChangeWatcher } ,
audio ::PlaybackMode ,
gltf ::Gltf ,
prelude ::* ,
tasks ::IoTaskPool ,
} ;
use bevy ::{ asset ::ChangeWatcher , prelude ::* } ;
use monologue_trees ::{ debug ::* , ui } ;
fn main ( ) {
App ::new ( )
. init_resource ::< AssetRegistry > ( )
. add_plugins ( (
DefaultPlugins
. set ( WindowPlugin {
@ -42,386 +31,75 @@ fn main() {
DebugInfoPlugin ,
ui ::GameUiPlugin { .. default ( ) } ,
) )
. add_systems ( Startup , ( init , init_assets_dir ) )
. add_systems (
Update ,
(
export . run_if ( interaction_condition ::< ExportAction > ) ,
clear . run_if ( interaction_condition ::< ClearAction > ) ,
import . run_if ( interaction_condition ::< ImportAction > ) ,
inspect . run_if ( interaction_condition ::< InspectAction > ) ,
load . run_if ( interaction_condition ::< LoadAssetsAction > ) ,
unload . run_if ( interaction_condition ::< UnloadAssetsAction > ) ,
spawn_level . run_if ( interaction_condition ::< SpawnLevelAction > ) ,
asset_inspector ::< AudioSource > ,
asset_inspector ::< Scene > ,
) ,
)
. add_systems (
PostUpdate ,
(
rehydrate ::< Visibility , ComputedVisibility > ,
rehydrate ::< Handle < AudioSource > , PlaybackSettings > ,
fallback_camera . run_if ( fallback_camera_condition ) ,
) ,
)
. register_type ::< LevelRoot > ( )
. register_type ::< Other > ( )
. register_type ::< Choice > ( )
. add_systems ( Startup , init )
. add_systems ( Update , serialize_debug . run_if ( pending ) )
. run ( ) ;
}
#[ derive(Debug, Component, Reflect, Defaul t)]
#[ derive(Debug, Component, Default, Reflect) ]
#[ reflect(Component) ]
struct LevelRoot ;
#[ derive(Debug, Component) ]
struct ExportAction ;
#[ derive(Debug, Component) ]
struct ClearAction ;
#[ derive(Debug, Component) ]
struct ImportAction ;
#[ derive(Debug, Component) ]
struct InspectAction ;
#[ derive(Debug, Component) ]
struct LoadAssetsAction ;
#[ derive(Debug, Component) ]
struct UnloadAssetsAction ;
#[ derive(Debug, Component) ]
struct SpawnLevelAction ;
#[ derive(Debug, Resource, Default) ]
struct AssetRegistry {
handles : Vec < HandleUntyped > ,
#[ derive(Debug, Component, Default, Reflect) ]
#[ reflect(Component) ]
struct Other {
inner : String ,
}
fn init_assets_dir ( ) {
IoTaskPool ::get ( )
. spawn ( async move {
match DirBuilder ::new ( ) . create ( "assets" ) {
Ok ( _ ) = > info ! ( "Created assets directory" ) ,
Err ( e ) = > warn ! ( "Error creating assets directory" , e ) ,
}
} )
. detach ( ) ;
#[ derive(Debug, Component, Default, Reflect) ]
#[ reflect(Component) ]
enum Choice {
#[ reflect(default) ]
#[ default ]
One ,
Two ,
Three ,
}
fn init ( mut commands : Commands ) {
commands . spawn ( (
Camera2dBundle { .. default ( ) } ,
UiCameraConfig { show_ui : true } ,
) ) ;
commands
. spawn ( (
NodeBundle {
style : Style {
top : Val ::Px ( 0.0 ) ,
right : Val ::Px ( 0.0 ) ,
margin : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
padding : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
border : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
position_type : PositionType ::Absolute ,
.. default ( )
} ,
background_color : Color ::WHITE . into ( ) ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
} ,
ui ::Select ::Action ,
) )
. with_children ( | parent | {
parent . spawn ( (
ButtonBundle {
style : Style {
margin : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
padding : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
border : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
.. default ( )
} ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
} ,
ui ::Title {
text : "Load Assets" . into ( ) ,
.. default ( )
} ,
LoadAssetsAction ,
) ) ;
parent . spawn ( (
ButtonBundle {
style : Style {
margin : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
padding : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
border : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
.. default ( )
} ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
} ,
ui ::Title {
text : "Dump Assets" . into ( ) ,
.. default ( )
} ,
UnloadAssetsAction ,
) ) ;
parent . spawn ( (
ButtonBundle {
style : Style {
margin : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
padding : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
border : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
.. default ( )
} ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
} ,
ui ::Title {
text : "Spawn Level" . into ( ) ,
.. default ( )
} ,
SpawnLevelAction ,
SpatialBundle { .. default ( ) } ,
LevelRoot ::default ( ) ,
Name ::new ( "Root Entity" ) ,
Other ::default ( ) ,
Choice ::default ( ) ,
) ) ;
parent . spawn ( (
ButtonBundle {
style : Style {
margin : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
padding : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
border : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
.. default ( )
} ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
} ,
ui ::Title {
text : "Export" . into ( ) ,
.. default ( )
} ,
ExportAction ,
) ) ;
parent . spawn ( (
ButtonBundle {
style : Style {
margin : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
padding : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
border : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
.. default ( )
} ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
} ,
ui ::Title {
text : "Clear" . into ( ) ,
.. default ( )
} ,
ClearAction ,
) ) ;
parent . spawn ( (
ButtonBundle {
style : Style {
margin : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
padding : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
border : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
.. default ( )
} ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
} ,
ui ::Title {
text : "Import" . into ( ) ,
.. default ( )
} ,
ImportAction ,
) ) ;
parent . spawn ( (
ButtonBundle {
style : Style {
margin : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
padding : UiRect ::all ( Val ::Px ( 5.0 ) ) ,
border : UiRect ::all ( Val ::Px ( 1.0 ) ) ,
.. default ( )
} ,
border_color : Color ::BLACK . into ( ) ,
.. default ( )
} ,
ui ::Title {
text : "Inspect" . into ( ) ,
.. default ( )
} ,
InspectAction ,
) ) ;
} ) ;
}
fn interaction_condition < T : Component > (
events : Query < & Interaction , ( Changed < Interaction > , With < T > ) > ,
) -> bool {
events
. iter ( )
. find ( | & interaction | * interaction = = Interaction ::Pressed )
. is_some ( )
fn serialize_debug ( query : Query < Entity , With < LevelRoot > > , world : & World ) {
let entities = query . iter ( ) . collect ( ) ;
print! ( "{}" , ser ( entities , world ) ) ;
}
fn rehydrate < W : Component , WO : Component + Default + std ::fmt ::Debug > (
events : Query < Entity , ( Added < W > , Without < WO > ) > ,
mut commands : Commands ,
) {
events . iter ( ) . for_each ( | entity | {
info ! ( "Rehydrating {:?}" , WO ::default ( ) ) ;
commands . entity ( entity ) . insert ( WO ::default ( ) ) ;
} ) ;
fn pending ( query : Query < Entity , With < LevelRoot > > , mut done : Local < bool > ) -> bool {
if ! * done {
if query . iter ( ) . len ( ) > 0 {
* done = true ;
} else {
* done = false ;
}
return * done ;
}
return false ;
}
fn ser (
root : & Query < Entity , With < LevelRoot > > ,
children : & Query < & Children > ,
world : & World ,
) -> String {
fn ser ( entities : Vec < Entity > , world : & World ) -> String {
let app_type_registry = world . resource ::< AppTypeRegistry > ( ) . clone ( ) ;
let mut builder = DynamicSceneBuilder ::from_world ( world . clone ( ) ) ;
builder . deny_all_resources ( ) ;
// builder.allow_all();
builder . deny ::< ComputedVisibility > ( ) ;
// Level administrivia
builder . allow ::< LevelRoot > ( ) ;
// Scene components
builder . allow ::< Handle < Scene > > ( ) ;
builder . allow ::< Visibility > ( ) ;
builder . allow ::< Transform > ( ) ;
builder . allow ::< GlobalTransform > ( ) ;
// Audio components
builder . allow ::< Handle < AudioSource > > ( ) ;
builder . allow ::< PlaybackSettings > ( ) ;
root . iter ( ) . for_each ( | r | {
// Extract the level root
builder . extract_entity ( r ) ;
// Extract all level root children
builder . extract_entities (
children
. get ( r )
. expect ( "Root has children" )
. iter ( )
. map ( | & entity | entity ) ,
) ;
} ) ;
let scene = builder . build ( ) ;
let scene = {
let mut builder = DynamicSceneBuilder ::from_world ( world . clone ( ) ) ;
builder
. allow_all_resources ( )
. allow_all ( )
. deny ::< ComputedVisibility > ( )
. extract_entities ( entities . into_iter ( ) ) ;
builder . build ( )
} ;
scene
. serialize_ron ( & app_type_registry )
. expect ( "Serialize scene" )
}
fn export ( root : Query < Entity , With < LevelRoot > > , children : Query < & Children > , world : & World ) {
info ! ( "Export level" ) ;
let serialized = ser ( & root , & children , world ) ;
IoTaskPool ::get ( )
. spawn ( async move {
// Write the scene RON data to file
File ::create ( format! ( "assets/output.scn.ron" ) )
. and_then ( | mut file | file . write ( serialized . as_bytes ( ) ) )
. expect ( "Error while writing scene to file" ) ;
} )
. detach ( ) ;
}
fn inspect ( root : Query < Entity , With < LevelRoot > > , children : Query < & Children > , world : & World ) {
info ! ( "Inexpect level" ) ;
let serialized = ser ( & root , & children , world ) ;
print! ( "{}" , serialized ) ;
}
fn clear ( root : Query < Entity , With < LevelRoot > > , mut commands : Commands ) {
info ! ( "Clearing level" ) ;
root . iter ( ) . for_each ( | entity | {
commands . entity ( entity ) . despawn_recursive ( ) ;
} ) ;
}
// TODO: Figure out how to import the same asset from a differnt source
// How do the plugins do it??
fn import ( mut commands : Commands , server : Res < AssetServer > ) {
info ! ( "Importing level" ) ;
let scene_handle : Handle < DynamicScene > = server . load ( "output.scn.ron" ) ;
commands . spawn ( (
LevelRoot ,
DynamicSceneBundle {
scene : scene_handle . clone ( ) ,
.. default ( )
} ,
) ) ;
}
fn fallback_camera_condition (
added : Query < Entity , Added < Camera > > ,
mut removed : RemovedComponents < Camera > ,
) -> bool {
added . iter ( ) . chain ( removed . iter ( ) ) . count ( ) > 0
}
fn fallback_camera (
mut ui_camera : Query < & mut Camera , With < Camera2d > > ,
cameras : Query < & mut Camera , Without < Camera2d > > ,
) {
ui_camera . single_mut ( ) . is_active = cameras . iter ( ) . len ( ) < = 0 ;
}
fn asset_inspector < T : Asset > ( mut events : EventReader < AssetEvent < T > > ) {
events . iter ( ) . for_each ( | event | match event {
AssetEvent ::Created { handle } = > info ! ( "Asset Created {:?}" , handle ) ,
AssetEvent ::Modified { handle } = > info ! ( "Asset Modified {:?}" , handle ) ,
AssetEvent ::Removed { handle } = > info ! ( "Asset Removed {:?}" , handle ) ,
} ) ;
}
// OK seems like `load_folder` does not automatically pick up added files
fn load ( mut registry : ResMut < AssetRegistry > , server : Res < AssetServer > ) {
info ! ( "Loading assets" ) ;
registry . handles = server . load_folder ( "./dynamic" ) . unwrap ( ) ;
info ! ( "Current files: {:?}" , registry . handles ) ;
}
fn unload ( mut registry : ResMut < AssetRegistry > , mut gltfs : ResMut < Assets < Gltf > > ) {
info ! ( "Unloading asstes" ) ;
registry . handles . clear ( ) ;
// This is required to clear scenes from asset cache
gltfs . clear ( ) ;
}
fn spawn_level ( mut commands : Commands , server : Res < AssetServer > ) {
commands
. spawn ( ( SpatialBundle { .. default ( ) } , LevelRoot ) )
. with_children ( | parent | {
parent . spawn ( AudioSourceBundle {
source : server . load ::< AudioSource , & str > ( "dynamic/Lake Sound 1.ogg" ) ,
settings : PlaybackSettings {
mode : PlaybackMode ::Loop ,
paused : false ,
.. default ( )
} ,
} ) ;
parent . spawn ( ( SceneBundle {
scene : server . load ( "dynamic/materials.glb#Scene0" ) ,
.. default ( )
} , ) ) ;
} ) ;
}