All engine plugin registrations now live in CoreGamePlugin::build(). build_app() is reduced to DefaultPlugins setup + CoreGamePlugin registration. sync_provider is threaded through CoreGamePlugin::new() via Mutex<Option<...>>. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+66
-130
@@ -18,25 +18,15 @@ use std::io::Write;
|
|||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::window::{MonitorSelection, PresentMode, WindowPosition};
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
use bevy::winit::{UpdateMode, WinitSettings};
|
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
use bevy::window::{Monitor, PrimaryMonitor, PrimaryWindow};
|
use bevy::window::{Monitor, PrimaryMonitor, PrimaryWindow};
|
||||||
|
use bevy::window::{MonitorSelection, PresentMode, WindowPosition};
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
use bevy::winit::WinitWindows;
|
use bevy::winit::WinitWindows;
|
||||||
use solitaire_data::{load_settings_from, provider_for_backend, settings_file_path, Settings};
|
#[cfg(target_os = "android")]
|
||||||
use solitaire_engine::{
|
use bevy::winit::{UpdateMode, WinitSettings};
|
||||||
register_theme_asset_sources, AchievementPlugin, AnalyticsPlugin, AnimationPlugin, AssetSourcesPlugin,
|
use solitaire_data::{Settings, load_settings_from, provider_for_backend, settings_file_path};
|
||||||
AudioPlugin, AutoCompletePlugin, AvatarPlugin, CardAnimationPlugin, CardPlugin, ChallengePlugin,
|
use solitaire_engine::{CoreGamePlugin, SyncProvider, register_theme_asset_sources};
|
||||||
CoreGamePlugin, CursorPlugin, DailyChallengePlugin, DiagnosticsHudPlugin, DifficultyPlugin,
|
|
||||||
FeedbackAnimPlugin, FontPlugin, GamePlugin, HelpPlugin, HomePlugin, HudPlugin, InputPlugin,
|
|
||||||
LeaderboardPlugin, OnboardingPlugin, PausePlugin, PlayBySeedPlugin, ProfilePlugin,
|
|
||||||
ProgressPlugin, RadialMenuPlugin, ReplayOverlayPlugin, ReplayPlaybackPlugin,
|
|
||||||
SafeAreaInsetsPlugin, SelectionPlugin, SettingsPlugin, SplashPlugin, StatsPlugin, SyncPlugin,
|
|
||||||
SyncProvider, SyncSetupPlugin, TablePlugin, ThemePlugin, ThemeRegistryPlugin, TimeAttackPlugin,
|
|
||||||
UiFocusPlugin, UiModalPlugin, UiTooltipPlugin, WeeklyGoalsPlugin, WinSummaryPlugin,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn load_settings() -> Settings {
|
fn load_settings() -> Settings {
|
||||||
settings_file_path()
|
settings_file_path()
|
||||||
@@ -87,7 +77,6 @@ fn build_app_with_settings(
|
|||||||
settings: Settings,
|
settings: Settings,
|
||||||
sync_provider: Box<dyn SyncProvider + Send + Sync>,
|
sync_provider: Box<dyn SyncProvider + Send + Sync>,
|
||||||
) -> App {
|
) -> App {
|
||||||
|
|
||||||
// Restore the previous window geometry if the player has one saved.
|
// Restore the previous window geometry if the player has one saved.
|
||||||
// Otherwise open at the platform default (1280×800, centred on the
|
// Otherwise open at the platform default (1280×800, centred on the
|
||||||
// primary monitor) — `apply_smart_default_window_size` will resize
|
// primary monitor) — `apply_smart_default_window_size` will resize
|
||||||
@@ -95,7 +84,7 @@ fn build_app_with_settings(
|
|||||||
// sessions don't end up with a comparatively tiny window.
|
// sessions don't end up with a comparatively tiny window.
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
let had_saved_geometry = settings.window_geometry.is_some();
|
let had_saved_geometry = settings.window_geometry.is_some();
|
||||||
let (window_resolution, window_position) = match settings.window_geometry {
|
let (window_resolution, window_position) = match settings.window_geometry.as_ref() {
|
||||||
Some(geom) => (
|
Some(geom) => (
|
||||||
(geom.width, geom.height).into(),
|
(geom.width, geom.height).into(),
|
||||||
WindowPosition::At(IVec2::new(geom.x, geom.y)),
|
WindowPosition::At(IVec2::new(geom.x, geom.y)),
|
||||||
@@ -111,124 +100,71 @@ fn build_app_with_settings(
|
|||||||
// The card-theme system's `themes://` asset source must be
|
// The card-theme system's `themes://` asset source must be
|
||||||
// registered *before* `DefaultPlugins` builds `AssetPlugin`,
|
// registered *before* `DefaultPlugins` builds `AssetPlugin`,
|
||||||
// because that plugin freezes the asset-source list at build
|
// because that plugin freezes the asset-source list at build
|
||||||
// time. The matching `AssetSourcesPlugin` (added below) finishes
|
// time. The matching `AssetSourcesPlugin` (registered by
|
||||||
// the wiring after `DefaultPlugins` by populating the embedded
|
// `CoreGamePlugin`) finishes the wiring after `DefaultPlugins`
|
||||||
// default theme into Bevy's `EmbeddedAssetRegistry`.
|
// by populating the embedded default theme into Bevy's
|
||||||
|
// `EmbeddedAssetRegistry`.
|
||||||
register_theme_asset_sources(&mut app);
|
register_theme_asset_sources(&mut app);
|
||||||
|
|
||||||
app
|
app.add_plugins(
|
||||||
.add_plugins(
|
DefaultPlugins
|
||||||
DefaultPlugins
|
.set(WindowPlugin {
|
||||||
.set(WindowPlugin {
|
primary_window: Some(Window {
|
||||||
primary_window: Some(Window {
|
title: "Ferrous Solitaire".into(),
|
||||||
title: "Ferrous Solitaire".into(),
|
// X11/Wayland WM_CLASS so taskbar managers group
|
||||||
// X11/Wayland WM_CLASS so taskbar managers group
|
// multiple windows of this app correctly.
|
||||||
// multiple windows of this app correctly.
|
name: Some("ferrous-solitaire".into()),
|
||||||
name: Some("ferrous-solitaire".into()),
|
resolution: window_resolution,
|
||||||
resolution: window_resolution,
|
position: window_position,
|
||||||
position: window_position,
|
// On Android, AutoVsync caps the GPU at the display
|
||||||
// On Android, AutoVsync caps the GPU at the display
|
// refresh rate (~60-90 fps). Without it the renderer
|
||||||
// refresh rate (~60-90 fps). Without it the renderer
|
// spins as fast as the hardware allows, keeping the
|
||||||
// spins as fast as the hardware allows, keeping the
|
// GPU fully loaded and draining the battery even when
|
||||||
// GPU fully loaded and draining the battery even when
|
// the game is completely idle.
|
||||||
// the game is completely idle.
|
//
|
||||||
//
|
// On desktop (X11 / Wayland) AutoNoVsync prefers
|
||||||
// On desktop (X11 / Wayland) AutoNoVsync prefers
|
// Mailbox (triple-buffered) and falls back to
|
||||||
// Mailbox (triple-buffered) and falls back to
|
// Immediate, eliminating the vsync stall that
|
||||||
// Immediate, eliminating the vsync stall that
|
// AutoVsync produces during continuous window resize.
|
||||||
// AutoVsync produces during continuous window resize.
|
// The game's frame budget is small enough that a few
|
||||||
// The game's frame budget is small enough that a few
|
// stray dropped frames from disabling vsync are
|
||||||
// stray dropped frames from disabling vsync are
|
// imperceptible on desktop.
|
||||||
// imperceptible on desktop.
|
#[cfg(target_os = "android")]
|
||||||
#[cfg(target_os = "android")]
|
present_mode: PresentMode::AutoVsync,
|
||||||
present_mode: PresentMode::AutoVsync,
|
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
present_mode: PresentMode::AutoNoVsync,
|
|
||||||
// Android windows always fill the screen; max_width/max_height
|
|
||||||
// default to 0.0, which panics Bevy's clamp when min > max.
|
|
||||||
#[cfg(not(target_os = "android"))]
|
|
||||||
resize_constraints: bevy::window::WindowResizeConstraints {
|
|
||||||
min_width: 800.0,
|
|
||||||
min_height: 600.0,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
..default()
|
|
||||||
}),
|
|
||||||
..default()
|
|
||||||
})
|
|
||||||
// The `assets/` directory lives at the workspace root, but
|
|
||||||
// on desktop Bevy resolves `AssetPlugin::file_path` relative
|
|
||||||
// to the binary package's `CARGO_MANIFEST_DIR`
|
|
||||||
// (`solitaire_app/`), so `cargo run -p solitaire_app` would
|
|
||||||
// miss the workspace-root `assets/` without a `../` prefix.
|
|
||||||
//
|
|
||||||
// On Android cargo-apk packages the same directory into the
|
|
||||||
// APK at `assets/` (via `[package.metadata.android].assets`
|
|
||||||
// in solitaire_app/Cargo.toml). Bevy's `AndroidAssetReader`
|
|
||||||
// is already rooted there, so any `file_path` other than the
|
|
||||||
// default makes it walk *out* of the APK's assets root and
|
|
||||||
// all loads fail silently — which is what produced the
|
|
||||||
// solid-red card-back fallback in the v0.22.3 screenshot.
|
|
||||||
.set(bevy::asset::AssetPlugin {
|
|
||||||
#[cfg(not(target_os = "android"))]
|
#[cfg(not(target_os = "android"))]
|
||||||
file_path: "../assets".to_string(),
|
present_mode: PresentMode::AutoNoVsync,
|
||||||
|
// Android windows always fill the screen; max_width/max_height
|
||||||
|
// default to 0.0, which panics Bevy's clamp when min > max.
|
||||||
|
#[cfg(not(target_os = "android"))]
|
||||||
|
resize_constraints: bevy::window::WindowResizeConstraints {
|
||||||
|
min_width: 800.0,
|
||||||
|
min_height: 600.0,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
)
|
..default()
|
||||||
.add_plugins(AssetSourcesPlugin)
|
})
|
||||||
.add_plugins(ThemePlugin)
|
// The `assets/` directory lives at the workspace root, but
|
||||||
.add_plugins(ThemeRegistryPlugin)
|
// on desktop Bevy resolves `AssetPlugin::file_path` relative
|
||||||
.add_plugins(FontPlugin)
|
// to the binary package's `CARGO_MANIFEST_DIR`
|
||||||
.add_plugins(GamePlugin)
|
// (`solitaire_app/`), so `cargo run -p solitaire_app` would
|
||||||
.add_plugins(TablePlugin)
|
// miss the workspace-root `assets/` without a `../` prefix.
|
||||||
.add_plugins(CardPlugin)
|
//
|
||||||
// Cursor-icon feedback is desktop-only; Android has no pointer cursor.
|
// On Android cargo-apk packages the same directory into the
|
||||||
// The drop-target highlight systems (update_drop_highlights,
|
// APK at `assets/` (via `[package.metadata.android].assets`
|
||||||
// update_drop_target_overlays) live in CursorPlugin but ARE useful
|
// in solitaire_app/Cargo.toml). Bevy's `AndroidAssetReader`
|
||||||
// on Android — they've been left running because their Bevy system
|
// is already rooted there, so any `file_path` other than the
|
||||||
// params compile and function on Android; only the CursorIcon insert
|
// default makes it walk *out* of the APK's assets root and
|
||||||
// is inert. Gate the whole plugin if the cursor APIs ever cause
|
// all loads fail silently — which is what produced the
|
||||||
// Android linker issues; for now it's harmless to leave it registered.
|
// solid-red card-back fallback in the v0.22.3 screenshot.
|
||||||
.add_plugins(CursorPlugin)
|
.set(bevy::asset::AssetPlugin {
|
||||||
.add_plugins(InputPlugin)
|
#[cfg(not(target_os = "android"))]
|
||||||
.add_plugins(RadialMenuPlugin)
|
file_path: "../assets".to_string(),
|
||||||
.add_plugins(SelectionPlugin)
|
..default()
|
||||||
.add_plugins(AnimationPlugin)
|
}),
|
||||||
.add_plugins(FeedbackAnimPlugin)
|
)
|
||||||
.add_plugins(CardAnimationPlugin)
|
.add_plugins(CoreGamePlugin::new(sync_provider));
|
||||||
.add_plugins(AutoCompletePlugin)
|
|
||||||
.add_plugins(ReplayPlaybackPlugin)
|
|
||||||
.add_plugins(ReplayOverlayPlugin)
|
|
||||||
.add_plugins(StatsPlugin::default())
|
|
||||||
.add_plugins(ProgressPlugin::default())
|
|
||||||
.add_plugins(AchievementPlugin::default())
|
|
||||||
.add_plugins(DailyChallengePlugin)
|
|
||||||
.add_plugins(WeeklyGoalsPlugin)
|
|
||||||
.add_plugins(ChallengePlugin)
|
|
||||||
.add_plugins(PlayBySeedPlugin)
|
|
||||||
.add_plugins(DifficultyPlugin)
|
|
||||||
.add_plugins(TimeAttackPlugin)
|
|
||||||
.add_plugins(SafeAreaInsetsPlugin)
|
|
||||||
.add_plugins(HudPlugin)
|
|
||||||
.add_plugins(HelpPlugin)
|
|
||||||
.add_plugins(HomePlugin::default())
|
|
||||||
.add_plugins(AvatarPlugin)
|
|
||||||
.add_plugins(ProfilePlugin)
|
|
||||||
.add_plugins(PausePlugin)
|
|
||||||
.add_plugins(SettingsPlugin::default())
|
|
||||||
.add_plugins(AudioPlugin)
|
|
||||||
.add_plugins(OnboardingPlugin)
|
|
||||||
.add_plugins(SyncPlugin::new(sync_provider))
|
|
||||||
.add_plugins(SyncSetupPlugin)
|
|
||||||
.add_plugins(AnalyticsPlugin)
|
|
||||||
.add_plugins(LeaderboardPlugin)
|
|
||||||
.add_plugins(WinSummaryPlugin)
|
|
||||||
.add_plugins(UiModalPlugin)
|
|
||||||
.add_plugins(UiFocusPlugin)
|
|
||||||
.add_plugins(UiTooltipPlugin)
|
|
||||||
.add_plugins(SplashPlugin)
|
|
||||||
.add_plugins(DiagnosticsHudPlugin)
|
|
||||||
.add_plugins(CoreGamePlugin);
|
|
||||||
|
|
||||||
// On Android the default WinitSettings use UpdateMode::Continuous for
|
// On Android the default WinitSettings use UpdateMode::Continuous for
|
||||||
// the focused window, which means Bevy renders as fast as possible even
|
// the focused window, which means Bevy renders as fast as possible even
|
||||||
|
|||||||
@@ -1,15 +1,101 @@
|
|||||||
//! Central plugin that groups all gameplay systems.
|
//! Central plugin that groups all gameplay plugins.
|
||||||
//!
|
//!
|
||||||
//! Register [`CoreGamePlugin`] once in the app instead of the individual
|
//! Register [`CoreGamePlugin`] once in the app instead of the individual
|
||||||
//! plugins. Systems are added here rather than directly in the app entry point.
|
//! plugins. Plugin registration lives here rather than directly in the app
|
||||||
|
//! entry point.
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
/// Groups all Ferrous Solitaire gameplay plugins.
|
use crate::{
|
||||||
pub struct CoreGamePlugin;
|
AchievementPlugin, AnalyticsPlugin, AnimationPlugin, AssetSourcesPlugin, AudioPlugin,
|
||||||
|
AutoCompletePlugin, AvatarPlugin, CardAnimationPlugin, CardPlugin, ChallengePlugin,
|
||||||
|
CursorPlugin, DailyChallengePlugin, DiagnosticsHudPlugin, DifficultyPlugin, FeedbackAnimPlugin,
|
||||||
|
FontPlugin, GamePlugin, HelpPlugin, HomePlugin, HudPlugin, InputPlugin, LeaderboardPlugin,
|
||||||
|
OnboardingPlugin, PausePlugin, PlayBySeedPlugin, ProfilePlugin, ProgressPlugin,
|
||||||
|
RadialMenuPlugin, ReplayOverlayPlugin, ReplayPlaybackPlugin, SafeAreaInsetsPlugin,
|
||||||
|
SelectionPlugin, SettingsPlugin, SplashPlugin, StatsPlugin, SyncPlugin, SyncProvider,
|
||||||
|
SyncSetupPlugin, TablePlugin, ThemePlugin, ThemeRegistryPlugin, TimeAttackPlugin,
|
||||||
|
UiFocusPlugin, UiModalPlugin, UiTooltipPlugin, WeeklyGoalsPlugin, WinSummaryPlugin,
|
||||||
|
};
|
||||||
|
|
||||||
impl Plugin for CoreGamePlugin {
|
/// Groups all Ferrous Solitaire gameplay plugins.
|
||||||
fn build(&self, _app: &mut App) {
|
pub struct CoreGamePlugin {
|
||||||
// Gameplay systems will be migrated here in follow-up issues.
|
sync_provider: Mutex<Option<Box<dyn SyncProvider + Send + Sync>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoreGamePlugin {
|
||||||
|
/// Create a new [`CoreGamePlugin`] with the sync provider used by [`SyncPlugin`].
|
||||||
|
pub fn new(sync_provider: Box<dyn SyncProvider + Send + Sync>) -> Self {
|
||||||
|
Self {
|
||||||
|
sync_provider: Mutex::new(Some(sync_provider)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for CoreGamePlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
let mut sync_provider = match self.sync_provider.lock() {
|
||||||
|
Ok(guard) => guard,
|
||||||
|
Err(poisoned) => poisoned.into_inner(),
|
||||||
|
};
|
||||||
|
let sync_provider = sync_provider
|
||||||
|
.take()
|
||||||
|
.expect("CoreGamePlugin::build called twice");
|
||||||
|
|
||||||
|
app.add_plugins(AssetSourcesPlugin)
|
||||||
|
.add_plugins(ThemePlugin)
|
||||||
|
.add_plugins(ThemeRegistryPlugin)
|
||||||
|
.add_plugins(FontPlugin)
|
||||||
|
.add_plugins(GamePlugin)
|
||||||
|
.add_plugins(TablePlugin)
|
||||||
|
.add_plugins(CardPlugin)
|
||||||
|
// Cursor-icon feedback is desktop-only; Android has no pointer cursor.
|
||||||
|
// The drop-target highlight systems (update_drop_highlights,
|
||||||
|
// update_drop_target_overlays) live in CursorPlugin but ARE useful
|
||||||
|
// on Android — they've been left running because their Bevy system
|
||||||
|
// params compile and function on Android; only the CursorIcon insert
|
||||||
|
// is inert. Gate the whole plugin if the cursor APIs ever cause
|
||||||
|
// Android linker issues; for now it's harmless to leave it registered.
|
||||||
|
.add_plugins(CursorPlugin)
|
||||||
|
.add_plugins(InputPlugin)
|
||||||
|
.add_plugins(RadialMenuPlugin)
|
||||||
|
.add_plugins(SelectionPlugin)
|
||||||
|
.add_plugins(AnimationPlugin)
|
||||||
|
.add_plugins(FeedbackAnimPlugin)
|
||||||
|
.add_plugins(CardAnimationPlugin)
|
||||||
|
.add_plugins(AutoCompletePlugin)
|
||||||
|
.add_plugins(ReplayPlaybackPlugin)
|
||||||
|
.add_plugins(ReplayOverlayPlugin)
|
||||||
|
.add_plugins(StatsPlugin::default())
|
||||||
|
.add_plugins(ProgressPlugin::default())
|
||||||
|
.add_plugins(AchievementPlugin::default())
|
||||||
|
.add_plugins(DailyChallengePlugin)
|
||||||
|
.add_plugins(WeeklyGoalsPlugin)
|
||||||
|
.add_plugins(ChallengePlugin)
|
||||||
|
.add_plugins(PlayBySeedPlugin)
|
||||||
|
.add_plugins(DifficultyPlugin)
|
||||||
|
.add_plugins(TimeAttackPlugin)
|
||||||
|
.add_plugins(SafeAreaInsetsPlugin)
|
||||||
|
.add_plugins(HudPlugin)
|
||||||
|
.add_plugins(HelpPlugin)
|
||||||
|
.add_plugins(HomePlugin::default())
|
||||||
|
.add_plugins(AvatarPlugin)
|
||||||
|
.add_plugins(ProfilePlugin)
|
||||||
|
.add_plugins(PausePlugin)
|
||||||
|
.add_plugins(SettingsPlugin::default())
|
||||||
|
.add_plugins(AudioPlugin)
|
||||||
|
.add_plugins(OnboardingPlugin)
|
||||||
|
.add_plugins(SyncPlugin::new(sync_provider))
|
||||||
|
.add_plugins(SyncSetupPlugin)
|
||||||
|
.add_plugins(AnalyticsPlugin)
|
||||||
|
.add_plugins(LeaderboardPlugin)
|
||||||
|
.add_plugins(WinSummaryPlugin)
|
||||||
|
.add_plugins(UiModalPlugin)
|
||||||
|
.add_plugins(UiFocusPlugin)
|
||||||
|
.add_plugins(UiTooltipPlugin)
|
||||||
|
.add_plugins(SplashPlugin)
|
||||||
|
.add_plugins(DiagnosticsHudPlugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user