@@ -126,6 +126,8 @@ struct App {
126126 disconnect_reason : Option < String > ,
127127
128128 game : Option < Game > ,
129+ /// 当前世界会话编号。进入 / 离开世界时递增,异步存档回写前用它识别过期结果。
130+ world_session_id : u64 ,
129131
130132 /// 上一帧 performance.now()(毫秒)
131133 last_time_ms : f64 ,
@@ -206,6 +208,7 @@ pub async fn start() -> Result<(), JsValue> {
206208 connecting_error : None ,
207209 disconnect_reason : None ,
208210 game : None ,
211+ world_session_id : 0 ,
209212 last_time_ms : now_ms ( ) ,
210213 fps_frames : 0 ,
211214 fps_accum : 0.0 ,
@@ -224,6 +227,57 @@ pub async fn start() -> Result<(), JsValue> {
224227 Ok ( ( ) )
225228}
226229
230+ /// 递增世界会话编号。所有跨 await 的世界相关异步任务都应捕获编号,回写前再次比对。
231+ fn bump_world_session ( a : & mut App ) -> u64 {
232+ a. world_session_id = a. world_session_id . wrapping_add ( 1 ) ;
233+ a. world_session_id
234+ }
235+
236+ /// 清掉当前世界运行时资源。Renderer 是跨状态常驻对象,必须显式清空世界 GPU 缓存。
237+ fn clear_world_runtime ( a : & mut App ) -> u64 {
238+ let session_id = bump_world_session ( a) ;
239+ a. game = None ;
240+ a. preload_state = None ;
241+ a. request_pointer_lock_next = false ;
242+ a. relayed_peers . clear ( ) ;
243+ a. notifications . clear ( ) ;
244+ a. perf = FramePerfStats :: default ( ) ;
245+ a. egui_events . borrow_mut ( ) . clear ( ) ;
246+ a. renderer . clear_world_cache ( ) ;
247+ a. input . borrow_mut ( ) . clear_held ( ) ;
248+ if let Some ( doc) = web_sys:: window ( ) . and_then ( |w| w. document ( ) ) {
249+ doc. exit_pointer_lock ( ) ;
250+ }
251+ session_id
252+ }
253+
254+ /// 开始新世界前的统一入口:先结束旧会话,再返回新会话编号给异步存档任务使用。
255+ fn prepare_world_start ( a : & mut App ) -> u64 {
256+ let session_id = clear_world_runtime ( a) ;
257+ a. disconnect_reason = None ;
258+ a. connecting_error = None ;
259+ session_id
260+ }
261+
262+ /// 回大厅时让下一帧 Lobby 重新读取 OPFS 世界列表,避免显示退出前的旧缓存。
263+ fn mark_lobby_saves_stale ( a : & mut App ) {
264+ a. lobby_state . saves_loaded = false ;
265+ a. lobby_state . saves_loading = false ;
266+ a. lobby_state . saved_worlds . clear ( ) ;
267+ a. lobby_state . selected_save = None ;
268+ }
269+
270+ /// 所有“返回大厅”路径的统一收口。
271+ fn return_to_lobby ( a : & mut App ) {
272+ clear_world_runtime ( a) ;
273+ a. state = AppState :: Lobby ;
274+ a. disconnect_reason = None ;
275+ a. connecting_error = None ;
276+ a. connecting_room_id . clear ( ) ;
277+ a. connecting_mode = GameMode :: Local ;
278+ mark_lobby_saves_stale ( a) ;
279+ }
280+
227281// ============================================================
228282// 主循环 & 事件
229283// ============================================================
@@ -953,26 +1007,29 @@ fn start_single_player(
9531007 let rd = game. settings . render_distance as i32 ;
9541008 let total = ( ( 2 * rd + 1 ) * ( 2 * rd + 1 ) ) as usize ;
9551009
956- let mut a = app. borrow_mut ( ) ;
957- a. game = Some ( game) ;
958- a. connecting_mode = GameMode :: Local ;
959- a. connecting_room_id = String :: new ( ) ;
960- a. connecting_error = None ;
961- a. preload_state = Some ( PreloadState {
962- total,
963- received : 0 ,
964- meshed : 0 ,
965- active : true ,
966- } ) ;
967- a. state = AppState :: Connecting ;
968- log:: info!( "[local] 进入加载界面,开始区块预载 (total={total})" ) ;
969- drop ( a) ;
1010+ let session_id = {
1011+ let mut a = app. borrow_mut ( ) ;
1012+ let session_id = prepare_world_start ( & mut a) ;
1013+ a. game = Some ( game) ;
1014+ a. connecting_mode = GameMode :: Local ;
1015+ a. connecting_room_id = String :: new ( ) ;
1016+ a. connecting_error = None ;
1017+ a. preload_state = Some ( PreloadState {
1018+ total,
1019+ received : 0 ,
1020+ meshed : 0 ,
1021+ active : true ,
1022+ } ) ;
1023+ a. state = AppState :: Connecting ;
1024+ log:: info!( "[local] 进入加载界面,开始区块预载 (total={total})" ) ;
1025+ session_id
1026+ } ;
9701027
9711028 // 如果有 save_key,使用 open_by_key 加载;否则创建新存档
9721029 if let Some ( key) = save_key {
973- attach_storage_for_load ( app. clone ( ) , key. to_string ( ) ) ;
1030+ attach_storage_for_load ( app. clone ( ) , key. to_string ( ) , session_id ) ;
9741031 } else {
975- attach_storage_for_new ( app. clone ( ) , seed) ;
1032+ attach_storage_for_new ( app. clone ( ) , seed, session_id ) ;
9761033 }
9771034}
9781035
@@ -1103,6 +1160,7 @@ fn start_host(
11031160 game. camera . position = game. physics . eye_position ( ) ;
11041161 game. camera . pitch = -0.4 ;
11051162
1163+ let session_id = prepare_world_start ( & mut a) ;
11061164 a. game = Some ( game) ;
11071165 a. state = AppState :: Connecting ;
11081166 a. connecting_mode = GameMode :: Host ;
@@ -1114,10 +1172,10 @@ fn start_host(
11141172
11151173 // 如果有 save_key,使用 open_by_key 加载;否则创建新存档
11161174 if let Some ( key) = save_key {
1117- attach_storage_for_load ( app. clone ( ) , key. to_string ( ) ) ;
1175+ attach_storage_for_load ( app. clone ( ) , key. to_string ( ) , session_id ) ;
11181176 } else {
11191177 // Host 模式:使用 room_id + seed 创建存档
1120- attach_storage_async ( app. clone ( ) , room_id. to_string ( ) , seed) ;
1178+ attach_storage_async ( app. clone ( ) , room_id. to_string ( ) , seed, session_id ) ;
11211179 }
11221180 }
11231181 Err ( e) => {
@@ -1151,6 +1209,7 @@ fn start_remote(app: &Rc<RefCell<App>>, room_id: &str, display_name: &str) {
11511209 game. camera . position = game. physics . eye_position ( ) ;
11521210 game. camera . pitch = -0.4 ;
11531211
1212+ prepare_world_start ( & mut a) ;
11541213 a. game = Some ( game) ;
11551214 a. state = AppState :: Connecting ;
11561215 a. connecting_mode = GameMode :: Remote ;
@@ -1166,7 +1225,7 @@ fn start_remote(app: &Rc<RefCell<App>>, room_id: &str, display_name: &str) {
11661225 }
11671226}
11681227
1169- fn attach_storage_async ( app : Rc < RefCell < App > > , room_id : String , seed : u64 ) {
1228+ fn attach_storage_async ( app : Rc < RefCell < App > > , room_id : String , seed : u64 , session_id : u64 ) {
11701229 wasm_bindgen_futures:: spawn_local ( async move {
11711230 let _ = OpfsStorage :: request_persistence ( ) . await ;
11721231 match OpfsStorage :: open ( & room_id, seed) . await {
@@ -1193,6 +1252,10 @@ fn attach_storage_async(app: Rc<RefCell<App>>, room_id: String, seed: u64) {
11931252 }
11941253 let quota = storage. quota ( ) . await ;
11951254 let mut a = app. borrow_mut ( ) ;
1255+ if a. world_session_id != session_id {
1256+ log:: debug!( "[storage] 丢弃过期 Host 存档加载结果" ) ;
1257+ return ;
1258+ }
11961259 if let Some ( g) = a. game . as_mut ( ) {
11971260 for ( pos, chunk) in loaded {
11981261 g. server . borrow_mut ( ) . load_chunk_from_storage ( pos, chunk) ;
@@ -1206,6 +1269,9 @@ fn attach_storage_async(app: Rc<RefCell<App>>, room_id: String, seed: u64) {
12061269 }
12071270 Err ( e) => {
12081271 let mut a = app. borrow_mut ( ) ;
1272+ if a. world_session_id != session_id {
1273+ return ;
1274+ }
12091275 if let Some ( g) = a. game . as_mut ( ) {
12101276 g. storage_error = Some ( format ! ( "{e:?}" ) ) ;
12111277 }
@@ -1215,7 +1281,7 @@ fn attach_storage_async(app: Rc<RefCell<App>>, room_id: String, seed: u64) {
12151281}
12161282
12171283/// 创建新存档(用当前时间戳 + seed 生成 key)
1218- fn attach_storage_for_new ( app : Rc < RefCell < App > > , seed : u64 ) {
1284+ fn attach_storage_for_new ( app : Rc < RefCell < App > > , seed : u64 , session_id : u64 ) {
12191285 wasm_bindgen_futures:: spawn_local ( async move {
12201286 let _ = OpfsStorage :: request_persistence ( ) . await ;
12211287 match OpfsStorage :: create_new ( seed) . await {
@@ -1242,6 +1308,10 @@ fn attach_storage_for_new(app: Rc<RefCell<App>>, seed: u64) {
12421308 }
12431309 let quota = storage. quota ( ) . await ;
12441310 let mut a = app. borrow_mut ( ) ;
1311+ if a. world_session_id != session_id {
1312+ log:: debug!( "[storage] 丢弃过期新存档加载结果" ) ;
1313+ return ;
1314+ }
12451315 if let Some ( g) = a. game . as_mut ( ) {
12461316 for ( pos, chunk) in loaded {
12471317 g. server . borrow_mut ( ) . load_chunk_from_storage ( pos, chunk) ;
@@ -1255,6 +1325,9 @@ fn attach_storage_for_new(app: Rc<RefCell<App>>, seed: u64) {
12551325 }
12561326 Err ( e) => {
12571327 let mut a = app. borrow_mut ( ) ;
1328+ if a. world_session_id != session_id {
1329+ return ;
1330+ }
12581331 if let Some ( g) = a. game . as_mut ( ) {
12591332 g. storage_error = Some ( format ! ( "{e:?}" ) ) ;
12601333 }
@@ -1264,7 +1337,7 @@ fn attach_storage_for_new(app: Rc<RefCell<App>>, seed: u64) {
12641337}
12651338
12661339/// 通过 key 加载已有存档
1267- fn attach_storage_for_load ( app : Rc < RefCell < App > > , key : String ) {
1340+ fn attach_storage_for_load ( app : Rc < RefCell < App > > , key : String , session_id : u64 ) {
12681341 wasm_bindgen_futures:: spawn_local ( async move {
12691342 let _ = OpfsStorage :: request_persistence ( ) . await ;
12701343 match OpfsStorage :: open_by_key ( & key) . await {
@@ -1291,6 +1364,10 @@ fn attach_storage_for_load(app: Rc<RefCell<App>>, key: String) {
12911364 }
12921365 let quota = storage. quota ( ) . await ;
12931366 let mut a = app. borrow_mut ( ) ;
1367+ if a. world_session_id != session_id {
1368+ log:: debug!( "[storage] 丢弃过期已有存档加载结果" ) ;
1369+ return ;
1370+ }
12941371 if let Some ( g) = a. game . as_mut ( ) {
12951372 for ( pos, chunk) in loaded {
12961373 g. server . borrow_mut ( ) . load_chunk_from_storage ( pos, chunk) ;
@@ -1304,6 +1381,9 @@ fn attach_storage_for_load(app: Rc<RefCell<App>>, key: String) {
13041381 }
13051382 Err ( e) => {
13061383 let mut a = app. borrow_mut ( ) ;
1384+ if a. world_session_id != session_id {
1385+ return ;
1386+ }
13071387 if let Some ( g) = a. game . as_mut ( ) {
13081388 g. storage_error = Some ( format ! ( "{e:?}" ) ) ;
13091389 }
@@ -1484,12 +1564,8 @@ fn render_connecting_frame(app: &Rc<RefCell<App>>, cw: u32, ch: u32) -> Result<(
14841564 } ;
14851565
14861566 if matches ! ( cancel, Some ( ConnectingAction :: Cancel ) ) {
1487- // 直接丢掉 game,回 Lobby
14881567 let mut a = app. borrow_mut ( ) ;
1489- a. game = None ;
1490- a. preload_state = None ;
1491- a. state = AppState :: Lobby ;
1492- a. connecting_error = None ;
1568+ return_to_lobby ( & mut a) ;
14931569 return Ok ( ( ) ) ;
14941570 }
14951571
@@ -1627,9 +1703,7 @@ fn render_disconnected_frame(app: &Rc<RefCell<App>>, cw: u32, ch: u32) -> Result
16271703
16281704 if matches ! ( action, ui:: disconnected:: DisconnectedAction :: BackToLobby ) {
16291705 let mut a = app. borrow_mut ( ) ;
1630- a. state = AppState :: Lobby ;
1631- a. disconnect_reason = None ;
1632- a. connecting_error = None ;
1706+ return_to_lobby ( & mut a) ;
16331707 return Ok ( ( ) ) ;
16341708 }
16351709
@@ -1787,13 +1861,11 @@ fn apply_room_event(app: &Rc<RefCell<App>>, ev: RoomEvent) {
17871861 }
17881862 RoomEvent :: Disconnected { reason } => {
17891863 log:: warn!( "[net] Disconnected: {reason}" ) ;
1864+ clear_world_runtime ( & mut a) ;
17901865 a. disconnect_reason = Some ( reason. clone ( ) ) ;
17911866 a. connecting_error = Some ( reason) ;
1792- a. preload_state = None ;
1793- a. relayed_peers . clear ( ) ;
17941867 // Phase 6:Connecting / InGame 失联都跳到 Disconnected 页让用户看到原因
17951868 a. state = AppState :: Disconnected ;
1796- a. game = None ;
17971869 }
17981870 RoomEvent :: RemoteLeft { peer_id } => {
17991871 log:: info!( "[net] RemoteLeft: peer {peer_id}" ) ;
@@ -2338,10 +2410,7 @@ fn render_game_frame(
23382410 }
23392411 if pause_exit_to_lobby {
23402412 let mut a = app. borrow_mut ( ) ;
2341- a. game = None ;
2342- a. state = AppState :: Lobby ;
2343- a. disconnect_reason = None ;
2344- a. preload_state = None ;
2413+ return_to_lobby ( & mut a) ;
23452414 return Ok ( ( ) ) ;
23462415 }
23472416
0 commit comments