Latest commit 8c81e81

use bevy::{
    app::{AppExit, ScheduleRunnerPlugin, ScheduleRunnerSettings},
    ecs::schedule::ReportExecutionOrderAmbiguities,
    log::LogPlugin,
    prelude::*,
    utils::Duration,
};
use rand::random;

/// 这是Bevy实体组件系统(ECS)的介绍
/// 所有Bevy应用程序的逻辑都是用ECS模式构建的,所以一定要注意!
/// 
/// 为什么用ECS?
/// 略
/// 
/// ECS定义:
/// 
/// 组件:Rust数据类型
/// 
/// 实体:组件的集合就是实体(本质是一个唯一的ID)
///     例如,Entity1{ Name("Alice"), Position(0, 0) },Entity2{ Name("Bil"), Position(10, 5) }
/// 
/// 资源:全局数据
///     例如,asset_storage,events,system state等
/// 
/// 系统:在实体、组件和资源上运行的逻辑
/// 

// 组件
//

// 游戏里有很多“玩家”,每个玩家都有“名字”
#[derive(Component)]
struct Player {
    name: String,
}

// 每个玩家都有“分数”
#[derive(Component)]
struct Score {
    value: usize,
}

// 资源
//

// 储存游戏信息
#[derive(Default)]
struct GameState {
    // 当前回合
    current_round: usize,
    // 玩家总数
    total_players: usize,
    // 胜利的玩家
    winning_player: Option<String>,
}

// 储存游戏规则
struct GameRules {
    // 胜利所需分数
    winning_score: usize,
    // 最大回合
    max_rounds: usize,
    // 最大玩家数量
    max_players: usize,
}

// 系统
//

// 这是最简单的一种系统
fn print_message_system() {
    println!("这游戏真好玩!");
}

// 系统也可以读取和修改资源。此系统每次更新都会开始新的“一轮”游戏:
// 提示:“mut”表示资源是“可变的”
// Res<GameRules>是只读的,ResMut<GameState>可以修改资源
fn new_round_system(game_rules: Res<GameRules>, mut game_state: ResMut<GameState>) {
    game_state.current_round += 1;
    println!(
        "Begin round {} of {}",
        game_state.current_round, game_rules.max_rounds
    );
}

// 访问带有“Player”和“Score”组件的实体,更新实体的分数
fn score_system(mut query: Query<(&Player, &mut Score)>) {
    for (player, mut score) in query.iter_mut() {
        let scored_a_point = random::<bool>();
        if scored_a_point {
            score.value += 1;
            println!("{} 得分!他们的分数是:{}", player.name, score.value);
        } else {
            println!("{}没有得分!他们的分数是:{}", player.name, score.value);
        }
    }
}

// 访问所有带有“Player”和“Score”组件的实体,也会访问“GameRules”资源,以确定玩家是否已经获胜
fn score_check_system(
    game_rules: Res<GameRules>,
    mut game_state: ResMut<GameState>,
    query: Query<(&Player, &Score)>,
) {
    for (player, score) in query.iter() {
        if score.value == game_rules.winning_score {
            game_state.winning_player = Some(player.name.clone());
        }
    }
}

// 如果满足条件,结束游戏,然后触发一个AppExit事件,App退出(查看“event.rs”示例)
fn game_over_system(
    game_rules: Res<GameRules>,
    game_state: Res<GameState>,
    mut app_exit_events: EventWriter<AppExit>,
) {
    if let Some(ref player) = game_state.winning_player {
        println!("{} 赢得游戏!", player);
        app_exit_events.send(AppExit);
    } else if game_state.current_round == game_rules.max_rounds {
        println!("回合结束。平局!");
        app_exit_events.send(AppExit);
    }

    println!();
}

// 这个系统在app启动时运行一次
// 只运行一次的系统一般用来创建游戏的初始状态(state),“启动”系统和“普通”系统的区别是它们的注册方式不同:
// “启动”系统:app.add_startup_system(startup_system)
// “普通”系统:app.add_system(normal_system)
fn startup_system(mut commands: Commands, mut game_state: ResMut<GameState>) {
    // 创建游戏规则资源
    commands.insert_resource(GameRules {
        max_rounds: 10,
        winning_score: 4,
        max_players: 4,
    });
    // 把一些玩家添加到“World”,玩家默认有0分
    commands.spawn_batch(vec![
        (
            Player {
                name: "Alice".to_string(),
            },
            Score { value: 0 },
        ),
        (
            Player {
                name: "Bob".to_string(),
            },
            Score { value: 0 },
        ),
    ]);
    // 设置最大玩家数量为“2”
    game_state.total_players = 2;
}

// 这个系统用命令缓冲区来(潜在地)在每次迭代时向游戏增加一个新玩家(也就是用command来修改world)
// 普通系统不能安全地直接访问World实例,因为实例是并行运行的
// World包含所有组件,所以在并行中修改它的任意部分都是不安全的
// 命令缓冲区可以把所有“修改”排列起来修改World,从而不用直接访问World
fn new_player_system(
    mut commands: Commands,
    game_rules: Res<GameRules>,
    mut game_state: ResMut<GameState>,
) {
    //  随机添加一个新玩家
    let add_new_player = random::<bool>();
    if add_new_player && game_state.total_players < game_rules.max_players {
        game_state.total_players += 1;
        commands.spawn_bundle((
            Player {
                name: format!("Player {}", game_state.total_players),
            },
            Score { value: 0 },
        ));
        println!("Player {} joined the game!", game_state.total_players);
    }
}

// 如果要对World或Resource进行完全的、即时的读/写访问,可以用“独占系统”
// 警告:本地线程会阻止其他系统的并行执行,直到线程完成,如果需要多线程性能,一般避免使用本地线程
#[allow(dead_code)]
 fn exclusive_player_system(world: &mut World) {
     // 和“new_player_system”的功能一样
     let total_players = world.get_resource_mut::<GameState>().unwrap().total_players;
     let should_add_player = {
         let game_rules = world.get_resource::<GameState>().unwrap();
         let add_new_player = random::<bool>();
         add_new_player && total_players < game_rules.max_players
     };
     // 随机添加一个新玩家
     if should_add_player {
         world.spawn().insert_bundle((
             Player {
                 name: format!("Player {}", total_players),
             },
             Score { value: 0 },
         ));
         let mut game_state = world.get_resource_mut::<GameState>().unwrap();
         game_state.total_players += 1;
     }
 }

// 有时系统需要单独的“本地”状态,Bevy的ECS为此提供了Local<T>资源
// Local<T>对系统来说是唯一的,并自动初始化(如果它们还不存在)
// 如果拥有某个系统的ID,也可以用`Resources::get_local()`直接从Resources集合中访问本地资源
// 仅在以下情况适用:
// 1.拥有同某个系统的多个实例,并且这些系统都需要单独的状态
// 2.已经拥有某个资源的全局版本,不想在当前的系统覆盖它
// 3.不想把系统要使用的资源注册成全局资源

#[derive(Default)]
struct State {
    counter: usize,
}

// 注意:这段代码的作用是为了说明问题所在,和这个游戏没有任何关系
#[allow(dead_code)]
fn local_state_system(mut state: Local<State>, query: Query<&Player, &Score>) {
    for (player, score) in query.iter() {
        println!("Processed: {} {}", player.name, score.value);
    }
    println!("this system ran {} times", state.counter);
    state.counter += 1;
}

#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
enum MyStage {
    BeforeRound,
    AfterRound,
}

#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
enum MyLabels {
    ScoreCheck,
}

// Bevy应用程序的入口
fn main() {
    // Bevy的App是用建造者模式来创建的,可以把系统、资源和插件添加到App
    App::new()
        // 可以这样把资源添加到app
        .insert_resource(State { counter: 0 })
        // 把“系统设置”作为资源,用来配置系统
        .insert_resource(ScheduleRunnerSettings::run_loop(Duration::from_secs(5)))
        // 插件只是一组app构造器的调用
        // 下面的插件会每5秒(上面已经设置为5秒)运行一次app的“system schedule”
        .add_plugin(ScheduleRunnerPlugin::default())
        // 实现了Default或FromResources特型的资源可以这样添加
        .init_resource::<GameState>()
        // startup系统会先于其他系统运行一次,一般包含用来初始化的代码(例如添加实体或资源等)
        .add_startup_system(startup_system)
        // system的调用会把Rust函数转换成ECS系统
        .add_system(print_message_system)
        // 系统执行顺序
        //
        // 系统属于阶段,阶段控制着每一tick内所有系统的执行策略和大致顺序
        // “startup阶段”(startup系统注册在这个阶段)总会在普通阶段开始之前完成,并且阶段内的所有系统都完成后才能进入下一阶段
        // 一旦每一个阶段都结束,就完成一个循环(main),然后开始新的循环
        //
        // 默认情况下,所有系统都是并行运行的,除非这个系统要对某个数据进行可变访问
        // 并行运行很有效率,但有时需要考虑顺序
        // 例如,“游戏结束”系统不应该与其他系统并行运行,而是在其他系统之后运行
        //
        // 与其把每个系统分割成不同阶段,不如给相关系统打上`.label`标签,然后用`.before`或`.after`方法来明确排序
        // 被排序的系统会形成“排序依赖”关系,然后按顺序执行
        //
        // 大多数情况下,打上排序标签比拆分系统带来更好的性能,因为这样调度算法有更多并行运行的机会
        // 但是,阶段仍然是必须的:阶段的结束是一个硬同步点(意味着没有系统在运行),在这里处理系统发出的`Commands`
        // 这样做的原因是,`Commands`可能会执行一些与运行中的系统不兼容的操作,例如生成或删除实体,添加或删除资源等
        //
        // `add_system(system)`默认把系统添加到UPDATE阶段,也可以手动指定阶段
        // 下面的代码相当于:`add_system(score_system)`
        .add_system_to_stage(CoreStage::Update, score_system)
        // 也可以创建新的阶段e,下面是这个游戏的阶段顺序
        // "before_round": new_player_system, new_round_system
        // "update": print_message_system, game_over_system
        // "after_round": score_check_system, game_over_system
        .add_stage_before(
            CoreStage::Update,
            MyStage::BeforeRound,
            SystemStage::parallel(),
        )
        .add_stage_after(
            CoreStage::Update,
            MyStage::AfterRound,
            SystemStage::parallel(),
        )
        .add_system_to_stage(MyStage::BeforeRound, new_round_system)
        .add_system_to_stage(MyStage::BeforeRound, new_player_system)
        .add_system_to_stage(
            MyStage::BeforeRound,
            exclusive_player_system.exclusive_system(),
        )
        // 用显式排序的约束来确保game_over系统运行在score_check_system后面。
        // 首先,用`.label`标记将要引用的系统,...
        .add_system_to_stage(
            MyStage::AfterRound,
            score_check_system.label(MyLabels::ScoreCheck),
        )
        // 然后用`.before`或`.after`来表明顺序
        .add_system_to_stage(
            MyStage::AfterRound,
            game_over_system.after(MyLabels::ScoreCheck),
        )
        // 检查系统是否存在执行顺序歧义:用LogPlugin检查控制台产生的输出,并把下面的资源添加到app
        // 请注意,检查器的报告不全是潜在问题,需要自行判断
        .add_plugin(LogPlugin::default())
        .insert_resource(ReportExecutionOrderAmbiguities)
        // 开始运行app
        .run();
}