fix OOS caused by sending data to soon

previously block_undo would clear the undo stack and send data even when its parameter is false

this commit also fixes a possible crash when dsu changed the gamestate during formula evauluation

also removed a is_simultanious_ since it basicialyl did the same thing as undo_blocked_
This commit is contained in:
gfgtdf 2024-11-12 02:10:30 +01:00 committed by sevu
parent cc2914c1c6
commit 33bc132215
4 changed files with 49 additions and 38 deletions

View file

@ -18,6 +18,8 @@
#include "random.hpp"
#include "mt_rng.hpp"
#include <functional>
namespace randomness
{
/**
@ -38,6 +40,26 @@ namespace randomness
mt_rng& generator_;
};
class rng_proxy : public randomness::rng
{
public:
using t_impl = std::function<uint32_t()>;
rng_proxy(t_impl&& impl)
: impl_(std::move(impl))
{
}
virtual ~rng_proxy() = default;
protected:
virtual uint32_t next_random_impl()
{
return impl_();
};
private:
t_impl impl_;
};
/**
RAII class to use rng_deterministic in the current scope.
*/

View file

@ -181,31 +181,31 @@ std::string synced_context::generate_random_seed()
return seed_val.str();
}
void synced_context::set_is_simultaneous()
void synced_context::block_undo(bool do_block, bool clear_undo)
{
resources::undo_stack->clear();
is_simultaneous_ = true;
}
if(!do_block) {
return;
}
is_undo_blocked_ = true;
void synced_context::block_undo(bool do_block)
{
is_undo_blocked_ |= do_block;
resources::undo_stack->clear();
if(clear_undo) {
resources::undo_stack->clear();
}
// Since the action cannot be undone, send it immidiately to the other players.
resources::controller->send_actions();
}
bool synced_context::undo_blocked()
{
// this method should only works in a synced context.
// this method only works in a synced context.
assert(!is_unsynced());
// if we sent data of this action over the network already, undoing is blocked.
// if we called the rng, undoing is blocked.
// if the game has ended, undoing is blocked.
// if the turn has ended undoing is blocked.
return is_simultaneous_
|| is_undo_blocked_
|| (is_synced() && (randomness::generator->get_random_calls() != 0))
// Important: once this function returned true, it has to return true for the rest of the duration of the current action
// otherwise OOS happens, so the following code in particular relies on the inability to revoke a [end_turn]/[endlevel]
return is_undo_blocked_
|| resources::controller->is_regular_game_end()
|| resources::gamedata->end_turn_forced();
}
@ -233,7 +233,12 @@ std::shared_ptr<randomness::rng> synced_context::get_rng_for_action()
{
const std::string& mode = resources::classification->random_mode;
if(mode == "deterministic" || mode == "biased") {
return std::make_shared<randomness::rng_deterministic>(resources::gamedata->rng());
auto get_rng = []() {
//rnd is nonundoable, even when the deterministic rng is used.
synced_context::block_undo(true, false);
return resources::gamedata->rng().get_next_random();
};
return std::make_shared<randomness::rng_proxy>(get_rng);
} else {
return std::make_shared<randomness::synced_rng>(generate_random_seed);
}
@ -262,16 +267,13 @@ config synced_context::ask_server_choice(const server_choice& sch)
return sch.local_choice();
}
set_is_simultaneous();
block_undo(true, false);
resources::controller->increase_server_request_number();
const bool is_mp_game = resources::controller->is_networked_mp();
bool did_require = false;
DBG_REPLAY << "ask_server for random_seed";
// As soon as random or similar is involved, undoing is impossible.
resources::undo_stack->clear();
// There might be speak or similar commands in the replay before the user input.
while(true) {
do_replay_handle();
@ -374,7 +376,6 @@ set_scontext_synced_base::set_scontext_synced_base()
assert(synced_context::get_synced_state() == synced_context::UNSYNCED);
synced_context::set_synced_state(synced_context::SYNCED);
synced_context::reset_is_simultaneous();
synced_context::reset_block_undo();
synced_context::set_last_unit_id(resources::gameboard->unit_id_manager().get_save_id());
synced_context::reset_undo_commands();

View file

@ -120,21 +120,12 @@ public:
/** @return A rng_deterministic if in determinsic mode otherwise a rng_synced. */
static std::shared_ptr<randomness::rng> get_rng_for_action();
/** @return whether we needed data from other clients about the action, in this case we need to send data about the current action to other clients. which means we cannot undo it. */
static bool is_simultaneous()
{
return is_simultaneous_;
}
/** Sets is_simultaneous_ = false, called when entering the synced context. */
static void reset_is_simultaneous()
{
is_simultaneous_ = false;
}
/** Sets is_simultaneous_ = true, called using a user choice that is not the currently playing side. */
static void set_is_simultaneous();
static void block_undo(bool do_block = true);
/**
* @a set this to false to prevent clearing the undo stack, this is important when we cannot change the gamestate
* becasue with dsu clearing the undo stack changes the gamestate, which we for example don't want during formula
* evaluation, when it used the dice operator. TODO: consider removing this parameter when dsu is removed.
*/
static void block_undo(bool do_block = true, bool clear_undo = true);
static void reset_block_undo()
{
is_undo_blocked_ = false;
@ -207,10 +198,7 @@ private:
* been sent over the network.
*
* false = we are on a local turn and haven't sent anything yet.
*
* TODO: it would be better if the following variable were not static.
*/
static inline bool is_simultaneous_ = false;
static inline bool is_undo_blocked_ = false;
/** Used to restore the unit id manager when undoing. */

View file

@ -253,7 +253,7 @@ user_choice_manager::user_choice_manager(const std::string &name, const mp_sync:
assert(!t.is_empty());
if(side != current_side_)
{
synced_context::set_is_simultaneous();
synced_context::block_undo();
}
}