예제 #1
0
void CNetClientTurnManager::NotifyFinishedOwnCommands(u32 turn)
{
	NETTURN_LOG((L"NotifyFinishedOwnCommands(%d)\n", turn));

	// Send message to the server
	CEndCommandBatchMessage msg;
	msg.m_TurnLength = DEFAULT_TURN_LENGTH_MP; // TODO: why do we send this?
	msg.m_Turn = turn;
	m_NetClient.SendMessage(&msg);
}
예제 #2
0
void CNetServerTurnManager::UninitialiseClient(int client)
{
	NETTURN_LOG((L"UninitialiseClient(client=%d)\n", client));

	ENSURE(m_ClientsReady.find(client) != m_ClientsReady.end());
	m_ClientsReady.erase(client);
	m_ClientsSimulated.erase(client);

	// Check whether we're ready for the next turn now that we're not
	// waiting for this client any more
	CheckClientsReady();
}
예제 #3
0
void CNetServerTurnManager::CheckClientsReady()
{
	// See if all clients (including self) are ready for a new turn
	for (std::map<int, u32>::iterator it = m_ClientsReady.begin(); it != m_ClientsReady.end(); ++it)
	{
		NETTURN_LOG((L"  %d: %d <=? %d\n", it->first, it->second, m_ReadyTurn));
		if (it->second <= m_ReadyTurn)
			return; // wasn't ready for m_ReadyTurn+1
	}

	// Advance the turn
	++m_ReadyTurn;

	NETTURN_LOG((L"CheckClientsReady: ready for turn %d\n", m_ReadyTurn));

	// Tell all clients that the next turn is ready
	CEndCommandBatchMessage msg;
	msg.m_TurnLength = m_TurnLength;
	msg.m_Turn = m_ReadyTurn;
	m_NetServer.Broadcast(&msg);
}
예제 #4
0
void CNetClientTurnManager::PostCommand(CScriptValRooted data)
{
	NETTURN_LOG((L"PostCommand()\n"));

	// Transmit command to server
	CSimulationMessage msg(m_Simulation2.GetScriptInterface(), m_ClientId, m_PlayerId, m_CurrentTurn + COMMAND_DELAY, data.get());
	m_NetClient.SendMessage(&msg);

	// Add to our local queue
	//AddCommand(m_ClientId, m_PlayerId, data, m_CurrentTurn + COMMAND_DELAY);
	// TODO: we should do this when the server stops sending our commands back to us
}
예제 #5
0
void CNetTurnManager::AddCommand(int client, int player, JS::HandleValue data, u32 turn)
{
	NETTURN_LOG((L"AddCommand(client=%d player=%d turn=%d)\n", client, player, turn));

	if (!(m_CurrentTurn < turn && turn <= m_CurrentTurn + COMMAND_DELAY + 1))
	{
		debug_warn(L"Received command for invalid turn");
		return;
	}

	m_QueuedCommands[turn - (m_CurrentTurn+1)][client].emplace_back(player, m_Simulation2.GetScriptInterface().GetContext(), data);
}
예제 #6
0
bool CNetTurnManager::UpdateFastForward()
{
	m_DeltaSimTime = 0;

	NETTURN_LOG((L"UpdateFastForward current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn));

	// Check that the next turn is ready for execution
	if (m_ReadyTurn <= m_CurrentTurn)
		return false;

	while (m_ReadyTurn > m_CurrentTurn)
	{
		// TODO: It would be nice to remove some of the duplication with Update()
		// (This is similar but doesn't call any Notify functions or update DeltaTime,
		// it just updates the simulation state)

		m_CurrentTurn += 1;

		m_Simulation2.FlushDestroyedEntities();

		// Put all the client commands into a single list, in a globally consistent order
		std::vector<SimulationCommand> commands;
		for (std::map<u32, std::vector<SimulationCommand> >::iterator it = m_QueuedCommands[0].begin(); it != m_QueuedCommands[0].end(); ++it)
		{
			commands.insert(commands.end(), std::make_move_iterator(it->second.begin()), std::make_move_iterator(it->second.end()));
		}
		m_QueuedCommands.pop_front();
		m_QueuedCommands.resize(m_QueuedCommands.size() + 1);

		m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);

		NETTURN_LOG((L"Running %d cmds\n", commands.size()));

		m_Simulation2.Update(m_TurnLength, commands);
	}

	return true;
}
예제 #7
0
void CNetServerTurnManager::NotifyFinishedClientCommands(int client, u32 turn)
{
	NETTURN_LOG((L"NotifyFinishedClientCommands(client=%d, turn=%d)\n", client, turn));

	// Must be a client we've already heard of
	ENSURE(m_ClientsReady.find(client) != m_ClientsReady.end());

	// Clients must advance one turn at a time
	ENSURE(turn == m_ClientsReady[client] + 1);
	m_ClientsReady[client] = turn;

	// Check whether this was the final client to become ready
	CheckClientsReady();
}
예제 #8
0
void CNetTurnManager::AddCommand(int client, int player, CScriptValRooted data, u32 turn)
{
	NETTURN_LOG((L"AddCommand(client=%d player=%d turn=%d)\n", client, player, turn));

	if (!(m_CurrentTurn < turn && turn <= m_CurrentTurn + COMMAND_DELAY + 1))
	{
		debug_warn(L"Received command for invalid turn");
		return;
	}

	SimulationCommand cmd;
	cmd.player = player;
	cmd.data = data;
	m_QueuedCommands[turn - (m_CurrentTurn+1)][client].push_back(cmd);
}
예제 #9
0
void CNetServerTurnManager::NotifyFinishedClientUpdate(int client, u32 turn, const std::string& hash)
{
	// Clients must advance one turn at a time
	ENSURE(turn == m_ClientsSimulated[client] + 1);
	m_ClientsSimulated[client] = turn;

	m_ClientStateHashes[turn][client] = hash;

	// Find the newest turn which we know all clients have simulated
	u32 newest = std::numeric_limits<u32>::max();
	for (std::map<int, u32>::iterator it = m_ClientsSimulated.begin(); it != m_ClientsSimulated.end(); ++it)
	{
		if (it->second < newest)
			newest = it->second;
	}

	// For every set of state hashes that all clients have simulated, check for OOS
	for (std::map<u32, std::map<int, std::string> >::iterator it = m_ClientStateHashes.begin(); it != m_ClientStateHashes.end(); ++it)
	{
		if (it->first > newest)
			break;

		// Assume the host is correct (maybe we should choose the most common instead to help debugging)
		std::string expected = it->second.begin()->second;

		for (std::map<int, std::string>::iterator cit = it->second.begin(); cit != it->second.end(); ++cit)
		{
			NETTURN_LOG((L"sync check %d: %d = %ls\n", it->first, cit->first, Hexify(cit->second).c_str()));
			if (cit->second != expected)
			{
				// Oh no, out of sync

				// Tell everyone about it
				CSyncErrorMessage msg;
				msg.m_Turn = it->first;
				msg.m_HashExpected = expected;
				m_NetServer.Broadcast(&msg);

				break;
			}
		}
	}

	// Delete the saved hashes for all turns that we've already verified
	m_ClientStateHashes.erase(m_ClientStateHashes.begin(), m_ClientStateHashes.lower_bound(newest+1));
}
예제 #10
0
void CNetClientTurnManager::NotifyFinishedUpdate(u32 turn)
{
	bool quick = !TurnNeedsFullHash(turn);
	std::string hash;
	{
		PROFILE3("state hash check");
		ENSURE(m_Simulation2.ComputeStateHash(hash, quick));
	}

	NETTURN_LOG((L"NotifyFinishedUpdate(%d, %hs)\n", turn, Hexify(hash).c_str()));

	m_Replay.Hash(hash, quick);

	// Send message to the server
	CSyncCheckMessage msg;
	msg.m_Turn = turn;
	msg.m_Hash = hash;
	m_NetClient.SendMessage(&msg);
}
예제 #11
0
void CNetTurnManager::OnSyncError(u32 turn, const CStr& expectedHash, std::vector<CSyncErrorMessage::S_m_PlayerNames>& playerNames)
{
	NETTURN_LOG((L"OnSyncError(%d, %hs)\n", turn, Hexify(expectedHash).c_str()));

	// Only complain the first time
	if (m_HasSyncError)
		return;

	bool quick = !TurnNeedsFullHash(turn);
	std::string hash;
	ENSURE(m_Simulation2.ComputeStateHash(hash, quick));

	OsPath path = psLogDir()/"oos_dump.txt";
	std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
	m_Simulation2.DumpDebugState(file);
	file.close();

	hash = Hexify(hash);
	const std::string& expectedHashHex = Hexify(expectedHash);

	DisplayOOSError(turn, hash, expectedHashHex, false, &playerNames, &path);
}
예제 #12
0
bool CNetTurnManager::Update(float frameLength, size_t maxTurns)
{
	m_DeltaTime += frameLength;

	// If we haven't reached the next turn yet, do nothing
	if (m_DeltaTime < 0)
		return false;

	NETTURN_LOG((L"Update current=%d ready=%d\n", m_CurrentTurn, m_ReadyTurn));

	// Check that the next turn is ready for execution
	if (m_ReadyTurn <= m_CurrentTurn)
	{
		// Oops, we wanted to start the next turn but it's not ready yet -
		// there must be too much network lag.
		// TODO: complain to the user.
		// TODO: send feedback to the server to increase the turn length.

		// Reset the next-turn timer to 0 so we try again next update but
		// so we don't rush to catch up in subsequent turns.
		// TODO: we should do clever rate adjustment instead of just pausing like this.
		m_DeltaTime = 0;

		return false;
	}

	maxTurns = std::max((size_t)1, maxTurns); // always do at least one turn

	for (size_t i = 0; i < maxTurns; ++i)
	{
		// Check that we've reached the i'th next turn
		if (m_DeltaTime < 0)
			break;

		// Check that the i'th next turn is still ready
		if (m_ReadyTurn <= m_CurrentTurn)
			break;

		NotifyFinishedOwnCommands(m_CurrentTurn + COMMAND_DELAY);

		m_CurrentTurn += 1; // increase the turn number now, so Update can send new commands for a subsequent turn

		// Clean up any destroyed entities since the last turn (e.g. placement previews
		// or rally point flags generated by the GUI). (Must do this before the time warp
		// serialization.)
		m_Simulation2.FlushDestroyedEntities();

		// Save the current state for rewinding, if enabled
		if (m_TimeWarpNumTurns && (m_CurrentTurn % m_TimeWarpNumTurns) == 0)
		{
			PROFILE("time warp serialization");
			std::stringstream stream;
			m_Simulation2.SerializeState(stream);
			m_TimeWarpStates.push_back(stream.str());
		}

		// Put all the client commands into a single list, in a globally consistent order
		std::vector<SimulationCommand> commands;
		for (std::map<u32, std::vector<SimulationCommand> >::iterator it = m_QueuedCommands[0].begin(); it != m_QueuedCommands[0].end(); ++it)
		{
			commands.insert(commands.end(), it->second.begin(), it->second.end());
		}
		m_QueuedCommands.pop_front();
		m_QueuedCommands.resize(m_QueuedCommands.size() + 1);

		m_Replay.Turn(m_CurrentTurn-1, m_TurnLength, commands);

		NETTURN_LOG((L"Running %d cmds\n", commands.size()));

		m_Simulation2.Update(m_TurnLength, commands);

		NotifyFinishedUpdate(m_CurrentTurn);

		// Set the time for the next turn update
		m_DeltaTime -= m_TurnLength / 1000.f;
	}

	return true;
}