1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
use bevy::{
asset::{AssetPlugin, UnapprovedPathMode},
ecs::error::ErrorContext,
image::{ImageAddressMode, ImagePlugin, ImageSamplerDescriptor},
prelude::*,
};
use jackdaw::prelude::*;
fn main() -> AppExit {
// Install a SIGINT/SIGTERM handler before anything else gets a
// chance to. Something in the dep tree (wgpu, gilrs, or one of
// their transitive deps) installs its own `ctrlc` handler that
// swallows the signal without propagating an exit intent; so
// by default Ctrl+C in the terminal is a no-op for jackdaw.
// Claiming the handler first with `std::process::exit(130)`
// guarantees Ctrl+C actually kills the process.
//
// Error ignored: if another handler has already been claimed by
// the time this runs, that's what bevy also reports ("Skipping
// installing Ctrl+C handler as one was already installed"),
// and we can't do anything about it from here.
let _ = ctrlc::set_handler(|| {
error!("jackdaw: received Ctrl+C, exiting");
std::process::exit(130);
});
let project_root = jackdaw::project::read_last_project()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_default());
// Picker is the default landing screen on every launch. The
// user clicks the project they want from recents (or scaffolds
// a new one), which then triggers the build + handoff. Two
// exceptions:
//
// - Respawn after a scaffold/install: the parent process did
// the build already, so we skip straight to the editor view
// for the just-scaffolded project.
// - `JACKDAW_AUTO_OPEN=1` env var: opt in to "re-open last
// project on launch" for power users who prefer that flow.
//
// We previously defaulted to auto-open; reverted because static
// game projects need a 5-10 minute build on first run, and
// auto-opening one means the user stares at a launcher window
// doing apparently nothing for several minutes. Showing the
// picker first lets the user explicitly choose to start that
// build.
let respawn_skip_build = std::env::var_os(jackdaw::restart::ENV_SKIP_INITIAL_BUILD).is_some();
let auto_open_opt_in = std::env::var_os("JACKDAW_AUTO_OPEN").is_some();
let auto_open = if respawn_skip_build {
jackdaw::project::read_last_project().map(|path| jackdaw::project_select::PendingAutoOpen {
path,
skip_build: true,
})
} else if auto_open_opt_in {
jackdaw::project::read_last_project()
.filter(|p| p.is_dir() && p.join("Cargo.toml").is_file())
.map(|path| jackdaw::project_select::PendingAutoOpen {
path,
skip_build: false,
})
} else {
None
};
let mut app = App::new();
app
// The default error handler panics, which we never *ever*
// want to happen to the editor. Log an error instead.
.set_error_handler(error_handler)
.add_plugins(
DefaultPlugins
.set(AssetPlugin {
file_path: project_root.join("assets").to_string_lossy().to_string(),
unapproved_path_mode: UnapprovedPathMode::Allow,
..default()
})
.set(ImagePlugin {
default_sampler: ImageSamplerDescriptor {
address_mode_u: ImageAddressMode::Repeat,
address_mode_v: ImageAddressMode::Repeat,
address_mode_w: ImageAddressMode::Repeat,
..ImageSamplerDescriptor::linear()
},
}),
)
// Ambient plugins added next to `DefaultPlugins`. The
// editor's `EditorCorePlugin` and `PhysicsSimulationPlugin`
// assert presence, so user `MyGamePlugin`s can add the
// same plugins without conflict.
.add_plugins((
avian3d::prelude::PhysicsPlugins::default(),
bevy_enhanced_input::prelude::EnhancedInputPlugin,
))
.add_plugins(editor_plugins)
.add_systems(OnEnter(jackdaw::AppState::Editor), spawn_scene);
if let Some(pending) = auto_open {
app.insert_resource(pending);
}
app.run()
}
/// Build the editor plugin for the prebuilt `jackdaw` binary.
///
/// The dylib loader is always on so users who drop extension `.so`/
/// `.dll`/`.dylib` files into their config directory don't need to
/// rebuild the editor. The in-tree example extensions in
/// `examples/*` are workspace members built as standalone cdylibs ;
/// point the loader at their build output if you want to exercise
/// them, rather than bundling them statically into the editor
/// binary.
fn editor_plugins(app: &mut App) {
app.add_plugins(EditorPlugins::default());
// DylibLoaderPlugin is launcher-only. Static editor binaries
// built from the static-game template use EditorPlugins WITHOUT
// this plugin, since their game code is statically linked and
// they have no business scanning `~/.config/jackdaw/games/`
// (where dylibs from incompatible bevy compilations may live).
app.add_plugins(DylibLoaderPlugin::default());
}
fn spawn_scene(mut commands: Commands) {
commands.queue(|world: &mut World| {
jackdaw::scene_io::spawn_default_lighting(world);
});
}
#[track_caller]
#[inline]
fn error_handler(error: BevyError, ctx: ErrorContext) {
let msg = format!("{error}");
if msg.contains("Note that interacting with a despawned entity is the most common cause of this error but there are others") {
// TODO: Ideally these should not happen. But as-is, we get a lot of them and they are benign, so let's not flood the logs
bevy::ecs::error::debug(error, ctx);
return;
}
bevy::ecs::error::error(error, ctx);
}