@@ -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// ============================================================
@@ -946,26 +1000,29 @@ fn start_single_player(
9461000 let rd = game. settings . render_distance as i32 ;
9471001 let total = ( ( 2 * rd + 1 ) * ( 2 * rd + 1 ) ) as usize ;
9481002
949- let mut a = app. borrow_mut ( ) ;
950- a. game = Some ( game) ;
951- a. connecting_mode = GameMode :: Local ;
952- a. connecting_room_id = String :: new ( ) ;
953- a. connecting_error = None ;
954- a. preload_state = Some ( PreloadState {
955- total,
956- received : 0 ,
957- meshed : 0 ,
958- active : true ,
959- } ) ;
960- a. state = AppState :: Connecting ;
961- log:: info!( "[local] 进入加载界面,开始区块预载 (total={total})" ) ;
962- drop ( a) ;
1003+ let session_id = {
1004+ let mut a = app. borrow_mut ( ) ;
1005+ let session_id = prepare_world_start ( & mut a) ;
1006+ a. game = Some ( game) ;
1007+ a. connecting_mode = GameMode :: Local ;
1008+ a. connecting_room_id = String :: new ( ) ;
1009+ a. connecting_error = None ;
1010+ a. preload_state = Some ( PreloadState {
1011+ total,
1012+ received : 0 ,
1013+ meshed : 0 ,
1014+ active : true ,
1015+ } ) ;
1016+ a. state = AppState :: Connecting ;
1017+ log:: info!( "[local] 进入加载界面,开始区块预载 (total={total})" ) ;
1018+ session_id
1019+ } ;
9631020
9641021 // 如果有 save_key,使用 open_by_key 加载;否则创建新存档
9651022 if let Some ( key) = save_key {
966- attach_storage_for_load ( app. clone ( ) , key. to_string ( ) ) ;
1023+ attach_storage_for_load ( app. clone ( ) , key. to_string ( ) , session_id ) ;
9671024 } else {
968- attach_storage_for_new ( app. clone ( ) , seed) ;
1025+ attach_storage_for_new ( app. clone ( ) , seed, session_id ) ;
9691026 }
9701027}
9711028
@@ -1096,6 +1153,7 @@ fn start_host(
10961153 game. camera . position = game. physics . eye_position ( ) ;
10971154 game. camera . pitch = -0.4 ;
10981155
1156+ let session_id = prepare_world_start ( & mut a) ;
10991157 a. game = Some ( game) ;
11001158 a. state = AppState :: Connecting ;
11011159 a. connecting_mode = GameMode :: Host ;
@@ -1107,10 +1165,10 @@ fn start_host(
11071165
11081166 // 如果有 save_key,使用 open_by_key 加载;否则创建新存档
11091167 if let Some ( key) = save_key {
1110- attach_storage_for_load ( app. clone ( ) , key. to_string ( ) ) ;
1168+ attach_storage_for_load ( app. clone ( ) , key. to_string ( ) , session_id ) ;
11111169 } else {
11121170 // Host 模式:使用 room_id + seed 创建存档
1113- attach_storage_async ( app. clone ( ) , room_id. to_string ( ) , seed) ;
1171+ attach_storage_async ( app. clone ( ) , room_id. to_string ( ) , seed, session_id ) ;
11141172 }
11151173 }
11161174 Err ( e) => {
@@ -1144,6 +1202,7 @@ fn start_remote(app: &Rc<RefCell<App>>, room_id: &str, display_name: &str) {
11441202 game. camera . position = game. physics . eye_position ( ) ;
11451203 game. camera . pitch = -0.4 ;
11461204
1205+ prepare_world_start ( & mut a) ;
11471206 a. game = Some ( game) ;
11481207 a. state = AppState :: Connecting ;
11491208 a. connecting_mode = GameMode :: Remote ;
@@ -1159,7 +1218,7 @@ fn start_remote(app: &Rc<RefCell<App>>, room_id: &str, display_name: &str) {
11591218 }
11601219}
11611220
1162- fn attach_storage_async ( app : Rc < RefCell < App > > , room_id : String , seed : u64 ) {
1221+ fn attach_storage_async ( app : Rc < RefCell < App > > , room_id : String , seed : u64 , session_id : u64 ) {
11631222 wasm_bindgen_futures:: spawn_local ( async move {
11641223 let _ = OpfsStorage :: request_persistence ( ) . await ;
11651224 match OpfsStorage :: open ( & room_id, seed) . await {
@@ -1186,6 +1245,10 @@ fn attach_storage_async(app: Rc<RefCell<App>>, room_id: String, seed: u64) {
11861245 }
11871246 let quota = storage. quota ( ) . await ;
11881247 let mut a = app. borrow_mut ( ) ;
1248+ if a. world_session_id != session_id {
1249+ log:: debug!( "[storage] 丢弃过期 Host 存档加载结果" ) ;
1250+ return ;
1251+ }
11891252 if let Some ( g) = a. game . as_mut ( ) {
11901253 for ( pos, chunk) in loaded {
11911254 g. server . borrow_mut ( ) . load_chunk_from_storage ( pos, chunk) ;
@@ -1199,6 +1262,9 @@ fn attach_storage_async(app: Rc<RefCell<App>>, room_id: String, seed: u64) {
11991262 }
12001263 Err ( e) => {
12011264 let mut a = app. borrow_mut ( ) ;
1265+ if a. world_session_id != session_id {
1266+ return ;
1267+ }
12021268 if let Some ( g) = a. game . as_mut ( ) {
12031269 g. storage_error = Some ( format ! ( "{e:?}" ) ) ;
12041270 }
@@ -1208,7 +1274,7 @@ fn attach_storage_async(app: Rc<RefCell<App>>, room_id: String, seed: u64) {
12081274}
12091275
12101276/// 创建新存档(用当前时间戳 + seed 生成 key)
1211- fn attach_storage_for_new ( app : Rc < RefCell < App > > , seed : u64 ) {
1277+ fn attach_storage_for_new ( app : Rc < RefCell < App > > , seed : u64 , session_id : u64 ) {
12121278 wasm_bindgen_futures:: spawn_local ( async move {
12131279 let _ = OpfsStorage :: request_persistence ( ) . await ;
12141280 match OpfsStorage :: create_new ( seed) . await {
@@ -1235,6 +1301,10 @@ fn attach_storage_for_new(app: Rc<RefCell<App>>, seed: u64) {
12351301 }
12361302 let quota = storage. quota ( ) . await ;
12371303 let mut a = app. borrow_mut ( ) ;
1304+ if a. world_session_id != session_id {
1305+ log:: debug!( "[storage] 丢弃过期新存档加载结果" ) ;
1306+ return ;
1307+ }
12381308 if let Some ( g) = a. game . as_mut ( ) {
12391309 for ( pos, chunk) in loaded {
12401310 g. server . borrow_mut ( ) . load_chunk_from_storage ( pos, chunk) ;
@@ -1248,6 +1318,9 @@ fn attach_storage_for_new(app: Rc<RefCell<App>>, seed: u64) {
12481318 }
12491319 Err ( e) => {
12501320 let mut a = app. borrow_mut ( ) ;
1321+ if a. world_session_id != session_id {
1322+ return ;
1323+ }
12511324 if let Some ( g) = a. game . as_mut ( ) {
12521325 g. storage_error = Some ( format ! ( "{e:?}" ) ) ;
12531326 }
@@ -1257,7 +1330,7 @@ fn attach_storage_for_new(app: Rc<RefCell<App>>, seed: u64) {
12571330}
12581331
12591332/// 通过 key 加载已有存档
1260- fn attach_storage_for_load ( app : Rc < RefCell < App > > , key : String ) {
1333+ fn attach_storage_for_load ( app : Rc < RefCell < App > > , key : String , session_id : u64 ) {
12611334 wasm_bindgen_futures:: spawn_local ( async move {
12621335 let _ = OpfsStorage :: request_persistence ( ) . await ;
12631336 match OpfsStorage :: open_by_key ( & key) . await {
@@ -1284,6 +1357,10 @@ fn attach_storage_for_load(app: Rc<RefCell<App>>, key: String) {
12841357 }
12851358 let quota = storage. quota ( ) . await ;
12861359 let mut a = app. borrow_mut ( ) ;
1360+ if a. world_session_id != session_id {
1361+ log:: debug!( "[storage] 丢弃过期已有存档加载结果" ) ;
1362+ return ;
1363+ }
12871364 if let Some ( g) = a. game . as_mut ( ) {
12881365 for ( pos, chunk) in loaded {
12891366 g. server . borrow_mut ( ) . load_chunk_from_storage ( pos, chunk) ;
@@ -1297,6 +1374,9 @@ fn attach_storage_for_load(app: Rc<RefCell<App>>, key: String) {
12971374 }
12981375 Err ( e) => {
12991376 let mut a = app. borrow_mut ( ) ;
1377+ if a. world_session_id != session_id {
1378+ return ;
1379+ }
13001380 if let Some ( g) = a. game . as_mut ( ) {
13011381 g. storage_error = Some ( format ! ( "{e:?}" ) ) ;
13021382 }
@@ -1477,12 +1557,8 @@ fn render_connecting_frame(app: &Rc<RefCell<App>>, cw: u32, ch: u32) -> Result<(
14771557 } ;
14781558
14791559 if matches ! ( cancel, Some ( ConnectingAction :: Cancel ) ) {
1480- // 直接丢掉 game,回 Lobby
14811560 let mut a = app. borrow_mut ( ) ;
1482- a. game = None ;
1483- a. preload_state = None ;
1484- a. state = AppState :: Lobby ;
1485- a. connecting_error = None ;
1561+ return_to_lobby ( & mut a) ;
14861562 return Ok ( ( ) ) ;
14871563 }
14881564
@@ -1620,9 +1696,7 @@ fn render_disconnected_frame(app: &Rc<RefCell<App>>, cw: u32, ch: u32) -> Result
16201696
16211697 if matches ! ( action, ui:: disconnected:: DisconnectedAction :: BackToLobby ) {
16221698 let mut a = app. borrow_mut ( ) ;
1623- a. state = AppState :: Lobby ;
1624- a. disconnect_reason = None ;
1625- a. connecting_error = None ;
1699+ return_to_lobby ( & mut a) ;
16261700 return Ok ( ( ) ) ;
16271701 }
16281702
@@ -1780,13 +1854,11 @@ fn apply_room_event(app: &Rc<RefCell<App>>, ev: RoomEvent) {
17801854 }
17811855 RoomEvent :: Disconnected { reason } => {
17821856 log:: warn!( "[net] Disconnected: {reason}" ) ;
1857+ clear_world_runtime ( & mut a) ;
17831858 a. disconnect_reason = Some ( reason. clone ( ) ) ;
17841859 a. connecting_error = Some ( reason) ;
1785- a. preload_state = None ;
1786- a. relayed_peers . clear ( ) ;
17871860 // Phase 6:Connecting / InGame 失联都跳到 Disconnected 页让用户看到原因
17881861 a. state = AppState :: Disconnected ;
1789- a. game = None ;
17901862 }
17911863 RoomEvent :: RemoteLeft { peer_id } => {
17921864 log:: info!( "[net] RemoteLeft: peer {peer_id}" ) ;
@@ -2331,10 +2403,7 @@ fn render_game_frame(
23312403 }
23322404 if pause_exit_to_lobby {
23332405 let mut a = app. borrow_mut ( ) ;
2334- a. game = None ;
2335- a. state = AppState :: Lobby ;
2336- a. disconnect_reason = None ;
2337- a. preload_state = None ;
2406+ return_to_lobby ( & mut a) ;
23382407 return Ok ( ( ) ) ;
23392408 }
23402409
0 commit comments