Skip to content

Commit 63868e7

Browse files
authored
Merge pull request #1969 from ZombieFreak115/proper-network-preload
Fix lockstep multiplayer save loading, reloading and oos recovery
2 parents 9abd742 + 860e81c commit 63868e7

28 files changed

+2179
-344
lines changed

assets/alice.gui

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4616,6 +4616,17 @@ guiTypes = {
46164616
position = { x= 148 y = 3 }
46174617
quadTextureSprite = "GFX_take_province"
46184618
}
4619+
4620+
instantTextBoxType = {
4621+
name = "ready_state"
4622+
extends = "ingame_multiplayer_entry"
4623+
position = { x=160 y =2}
4624+
borderSize = { 0 0}
4625+
maxsize = { 50 32 }
4626+
textureFile = ""
4627+
font = "vic_18_black"
4628+
}
4629+
46194630
guiButtonType = {
46204631
name = "button_ban"
46214632
extends = "ingame_multiplayer_entry"
@@ -4625,6 +4636,16 @@ guiTypes = {
46254636
delayedTooltipText = ""
46264637
position = { x=228 y =2}
46274638
}
4639+
4640+
guiButtonType = {
4641+
name = "resync_button"
4642+
extends = "ingame_lobby_window"
4643+
quadTextureSprite = "GFX_alice_event_auto"
4644+
position = { x=535 y =314}
4645+
}
4646+
4647+
4648+
46284649
guiButtonType = {
46294650
name = "alice_budget_warning"
46304651
extends = "topbar"

assets/localisation/en-US/alice.csv

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,6 +1035,7 @@ tech_year_tt;?G$x$?! from the current year
10351035
disband_all;Disband all selected units
10361036
alice_play_checksum_host;Checksum mismatch with host, ensure you have the same mods and savefiles
10371037
alice_play_save_stream;Host is streaming a save to you, wait until it completes
1038+
alice_no_start_game_player_loading;Cannot start the game while players are loading
10381039
ai_will_accept_po;They will accept this offer
10391040
ai_will_not_accept_po;They will NOT accept this offer
10401041
close_and_del;Close & Delete Factory
@@ -1186,6 +1187,7 @@ explain_colonial_points;Secondary and Great Powers accumulate colonial points.
11861187
alice_lobby_back;Back to lobby
11871188
alice_lobby_back_tt_1;You're already in the fucking lobby
11881189
alice_lobby_back_tt_2;Only the host may go back to lobby
1190+
alice_lobby_back_player_loading;You may not go back to the lobby while players are loading
11891191
reopen_with_tab;You can reopen this window with ?YTAB?W
11901192
alice_topbar_tab_1;Open or close this tab
11911193
alice_topbar_tab_2;Open or close the population tab
@@ -1532,3 +1534,9 @@ pop_migration_attraction_wage_ratio;And the wage difference multipler: $x$
15321534
pop_migration_attraction_bureaucracy;Bureaucracy base attraction: $x$
15331535
alice_toggle_administration;Establish or abolish local administration. Administrations outside your control will not work. Consult administrative map mode to check existing administrations.
15341536
alice_unit_target;===> $x$
1537+
alice_lobby_resync;Resync lobby
1538+
alice_lobby_resync_players_loading;Cannot resync when players are loading
1539+
alice_lobby_resync_no_oos;No one is OOS
1540+
alice_lobby_resync_not_host;Only the host may resync
1541+
alice_host_has_resync;Host has resynced the lobby
1542+

docs/multiplayer_technical.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,34 @@ The standard C++ and C library provide `sin`, `cos`, and `acos` functions for pe
2020

2121
### Notification commands
2222

23-
`notify_player_joins` - Tells the clients that a player has joined, marks the `source` nation as player-controlled.
23+
`notify_player_joins` - Tells the clients that a player has joined, marks the `source` nation as player-controlled. When a player joins, all other players in the lobby (including the host) will fully reset, then reload their gamestate. Currently this is done so that there is no "lingering" data left behind after reloading their own state. This is to futureproof it against even minor mistakes/changes in contributions which can easily breka sync if state is not fully reset first.
2424
`notify_player_pick_nation` - Picks a nation, this is useful for example on the lobby where players are switching nations constantly, IF the `source` is invalid (i.e a `dcon::nation_id{}`) then it refers to the current local player nation of the client, this is useful to set the "temporal nation" on the lobby so that clients can be identified by their nation automatically assigned by the server. Otherwise the `source` is the client who requested to pick a nation `target` in `data.nation_pick.target`.
25-
`notify_save_loaded` - Updates the session checksum, used to check discrepancies between clients and hosts that could hinder gameplay and throw it into an invalid state. Following it comes an `uint32_t` describing the size of the save stream, and the save stream itself!
25+
`notify_save_loaded` - Updates the session checksum, used to check discrepancies between clients and hosts that could hinder gameplay and throw it into an invalid state. Following it comes an `uint32_t` describing the size of the save stream, and the save stream itself! Keep in mind that the client will automatically reload their state first before loading the save
2626
`notify_player_kick` - When kicking a player, it is disconnected, but allowed to rejoin.
2727
`notify_player_ban` - When banning a player, it is disconnected, and not allowed to rejoin.
2828
`notify_start_game` - Host has started the game, all players connected will be sent into the game.
2929
`notify_stop_game` - Host has stopped the game (not paused), all players connected will be sent into the lobby.
3030
`notify_pause_game` - Host has paused the game, exists mainly to notify clients that the host has paused the game.
3131
`notify_reload` - Perform a game state reload as if it was a savefile.
32+
`notify_player_is_loading` - Sent by the host to (usually) all of the clients to notify that `source` is currently loading. The host will NOT process most commands while more than 0 clients are loading. This command may be sent to the loading client itself. The command is sent out after a reload or save stream is requested, or when a new player joins a lobby with a save.
33+
`notify_notify_player_fully_loaded` - Sent by the client to notify the host, and then re-broadcast to all clients that `source` has finished loading.
3234

3335
The server will send new clients a `notify_player_joins` for each connected player. It will send a `notify_player_pick_nation` to the client, with an invalid source, telling it what is their "assigned nation".
3436

3537
An assigned nation is a "random" nation that the server will hand out to the client so it can identifiably connect to the server as a nation, and perform commands as such nation.
3638

39+
When a state-reset&reload is happening, it will block rendering updates in the meantime to prevent crashes or other oddities happening while the state is completly empty. This is also the case when the `notify_stop_game` or `notify_start_game` are executed, it will block rendering updates until the scene change has been executed.
40+
3741
### Save streams
3842

3943
We send a copy of the save to the client, ultra-compressed, to permit it to connect without having to use external toolage, this is done for example when the host is loading a savefile - the client is given the new data of the savefile to keep them in sync.
4044

45+
### Re-sync
46+
47+
The lobby can be resyncronized if one or more players are OOS, and must be done manually by the host in the "tab" lobby screen.
48+
When a resync starts, it will send the notify_save_loaded command to the oos'd clients together with a save for them to load. The non-oos clients will receive a notify_reload command, and will reload their own save.
49+
50+
4151
### Hot-join
4252

4353
A new functionality is hotjoining to running sessions - the client may connect to the host and the host will assign them a random nation, if they wish to change their nation then they'll have to ask the host to go back to the lobby.

src/economy/demographics.cpp

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,22 +193,30 @@ void sum_over_demographics(sys::state& state, dcon::demographics_key key, F cons
193193
// sum in province
194194
state.world.for_each_pop([&](dcon::pop_id p) {
195195
auto location = state.world.pop_get_province_from_pop_location(p);
196-
state.world.province_get_demographics(location, key) += source(state, p);
196+
auto current = state.world.province_get_demographics(location, key);
197+
state.world.province_set_demographics(location, key, current + source(state, p));
197198
});
198199
// clear state
199200
state.world.execute_serial_over_state_instance(
200201
[&](auto si) { state.world.state_instance_set_demographics(si, key, ve::fp_vector()); });
201202
// sum in state
202203
province::for_each_land_province(state, [&](dcon::province_id p) {
203204
auto location = state.world.province_get_state_membership(p);
204-
state.world.state_instance_get_demographics(location, key) += state.world.province_get_demographics(p, key);
205+
// check if province is uncolonized, as uncolonized provinces do not have valid state membership
206+
if(location) {
207+
state.world.state_instance_get_demographics(location, key) += state.world.province_get_demographics(p, key);
208+
}
205209
});
206210
// clear nation
207211
state.world.execute_serial_over_nation([&](auto ni) { state.world.nation_set_demographics(ni, key, ve::fp_vector()); });
208212
// sum in nation
209213
state.world.for_each_state_instance([&](dcon::state_instance_id s) {
210214
auto location = state.world.state_instance_get_nation_from_state_ownership(s);
211-
state.world.nation_get_demographics(location, key) += state.world.state_instance_get_demographics(s, key);
215+
// check if state is not owned by a nation
216+
if(location) {
217+
state.world.nation_get_demographics(location, key) += state.world.state_instance_get_demographics(s, key);
218+
}
219+
212220
});
213221
}
214222

@@ -219,22 +227,31 @@ void alt_sum_over_demographics(sys::state& state, dcon::demographics_key key, F
219227
// sum in province
220228
state.world.for_each_pop([&](dcon::pop_id p) {
221229
auto location = state.world.pop_get_province_from_pop_location(p);
222-
state.world.province_get_demographics_alt(location, key) += source(state, p);
230+
auto current = state.world.province_get_demographics_alt(location, key);
231+
state.world.province_set_demographics_alt(location, key, current + source(state, p));
223232
});
224233
// clear state
225234
state.world.execute_serial_over_state_instance(
226235
[&](auto si) { state.world.state_instance_set_demographics_alt(si, key, ve::fp_vector()); });
227236
// sum in state
228237
province::for_each_land_province(state, [&](dcon::province_id p) {
229238
auto location = state.world.province_get_state_membership(p);
230-
state.world.state_instance_get_demographics_alt(location, key) += state.world.province_get_demographics_alt(p, key);
239+
// check if province is uncolonized, as uncolonized provinces do not have valid state membership
240+
if(location) {
241+
state.world.state_instance_get_demographics_alt(location, key) += state.world.province_get_demographics_alt(p, key);
242+
}
243+
231244
});
232245
// clear nation
233246
state.world.execute_serial_over_nation([&](auto ni) { state.world.nation_set_demographics_alt(ni, key, ve::fp_vector()); });
234247
// sum in nation
235248
state.world.for_each_state_instance([&](dcon::state_instance_id s) {
236249
auto location = state.world.state_instance_get_nation_from_state_ownership(s);
237-
state.world.nation_get_demographics_alt(location, key) += state.world.state_instance_get_demographics_alt(s, key);
250+
// check if state is not owned by a nation
251+
if(location) {
252+
state.world.nation_get_demographics_alt(location, key) += state.world.state_instance_get_demographics_alt(s, key);
253+
}
254+
238255
});
239256
}
240257

src/economy/economy.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,7 +2540,7 @@ void daily_update(sys::state& state, bool presimulation, float presimulation_sta
25402540
break;
25412541
}
25422542
});
2543-
2543+
25442544
populate_construction_consumption(state);
25452545

25462546
sanity_check(state);
@@ -2951,7 +2951,7 @@ void daily_update(sys::state& state, bool presimulation, float presimulation_sta
29512951
);
29522952
});
29532953
});
2954-
});
2954+
});
29552955

29562956
sanity_check(state);
29572957

@@ -3279,6 +3279,9 @@ void daily_update(sys::state& state, bool presimulation, float presimulation_sta
32793279

32803280
// STEP 4 national budget updates
32813281
for(auto n : state.nations_by_rank) {
3282+
if(!n) {
3283+
continue;
3284+
}
32823285
spent_on_construction_buffer.set(n, 0.f);
32833286

32843287
auto cap_prov = state.world.nation_get_capital(n);

src/economy/economy_stats.cpp

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ void register_demand(
3333
economy_reason reason
3434
) {
3535
assert(amount >= 0.f);
36-
state.world.market_get_demand(s, commodity_type) += amount;
36+
auto current = state.world.market_get_demand(s, commodity_type);
37+
state.world.market_set_demand(s, commodity_type, current + amount);
3738
assert(std::isfinite(state.world.market_get_demand(s, commodity_type)));
3839
}
3940

@@ -169,16 +170,20 @@ void register_intermediate_demand(
169170
float amount,
170171
economy_reason reason
171172
) {
172-
register_demand(state, s, c, amount, reason);
173-
state.world.market_set_intermediate_demand(
174-
s,
175-
c,
176-
state.world.market_get_intermediate_demand(s, c) + amount
177-
);
178-
auto local_price = price(state, s, c);
179-
auto median_price = state.world.commodity_get_median_price(c);
180-
auto sat = state.world.market_get_demand_satisfaction(s, c);
181-
state.world.market_set_gdp(s, state.world.market_get_gdp(s) - amount * median_price * sat);
173+
// check for market validity before writing data to it
174+
if(s) {
175+
register_demand(state, s, c, amount, reason);
176+
state.world.market_set_intermediate_demand(
177+
s,
178+
c,
179+
state.world.market_get_intermediate_demand(s, c) + amount
180+
);
181+
auto local_price = price(state, s, c);
182+
auto median_price = state.world.commodity_get_median_price(c);
183+
auto sat = state.world.market_get_demand_satisfaction(s, c);
184+
state.world.market_set_gdp(s, state.world.market_get_gdp(s) - amount * median_price * sat);
185+
}
186+
182187
}
183188

184189
void register_domestic_supply(

0 commit comments

Comments
 (0)