Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Game_sync.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ void Game::syncStep(Sint32 localTeam)

Uint64 startTick=SDL_GetTicks64();

map.stepGradients();

for (int i=0; i<mapHeader.getNumberOfTeams(); i++)
teams[i]->syncStep();

Expand Down
39 changes: 39 additions & 0 deletions src/map/Map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,52 @@ Map::Map()
fertilityMaximum = 0;
}


#ifndef YOG_SERVER_ONLY
// Draining the field jobs lives here, next to clear(), so every build that links
// Map.cpp can free a Map; the scheduling and the worker are in MapStep.cpp.

void Map::publishGradients(Uint32 now)
{
std::unique_lock<std::mutex> lock(gradientMutex);
while (!gradientJobs.empty() && gradientJobs.front().publishTick <= now)
{
gradientWake.wait(lock, [this] { return gradientJobs.front().done; });
GradientJob job = gradientJobs.front();
gradientJobs.pop_front();
std::swap(*job.slot, job.buffer);
spareGradients.push_back(job.buffer);
}
}

void Map::finishPendingGradients()
{
publishGradients(0xFFFFFFFF);
if (gradientWorker.joinable())
{
{
std::lock_guard<std::mutex> lock(gradientMutex);
gradientWorkerQuit = true;
gradientWake.notify_all();
}
gradientWorker.join();
}
for (Uint16 *spare : spareGradients)
delete[] spare;
spareGradients.clear();
}
#endif // !YOG_SERVER_ONLY

Map::~Map(void)
{
clear();
}

void Map::clear()
{
#ifndef YOG_SERVER_ONLY
finishPendingGradients();
#endif
// A failed load can own only a subset of these arrays.
for (int t=0; t<Team::MAX_COUNT; ++t)
{
Expand Down
49 changes: 47 additions & 2 deletions src/map/Map.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
#pragma once

#include <list>
#include <thread>
#include <deque>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <optional>
#include <assert.h>

Expand Down Expand Up @@ -130,6 +135,11 @@ class Map
#ifndef YOG_SERVER_ONLY
//! Do a step associated with map (grow resources and process bullets)
void syncStep(Uint32 stepCounter);
//! Refresh the resource and area fields round robin, one per tick: publishes the
//! field due this tick, then seeds the next one from the current map and hands it to
//! the worker thread, which propagates it while the units step. A field is seeded and
//! published at fixed ticks, so its content never depends on thread timing.
void stepGradients();
#endif // !YOG_SERVER_ONLY
//! Switch the Fog of War bufferResourceType
void switchFogOfWar(void);
Expand Down Expand Up @@ -627,9 +637,15 @@ class Map
void updateGlobalGradient(Uint8 *gradient);
//! Dijkstra on a freshly seeded field (see MapInternal.h). Seed costs must be
//! between 0 and the largest terrain step (currently 42); do not pass a completed
//! field. Uses shared scratch storage: calls across all Maps must be serial and
//! non-reentrant. swimClass must be in [0, SWIM_CLASS_COUNT).
//! field. swimClass must be in [0, SWIM_CLASS_COUNT). This overload is for the main
//! thread and uses its scratch; the worker thread passes its own scratch below, so
//! two fields can propagate concurrently.
void propagateGradient(Uint16 *gradient, int swimClass);
struct GradientScratch;
struct GradientScratchDeleter { void operator()(GradientScratch *scratch) const; };
typedef std::unique_ptr<GradientScratch, GradientScratchDeleter> GradientScratchPtr;
void propagateGradient(Uint16 *gradient, int swimClass, GradientScratch &scratch);
static GradientScratchPtr newGradientScratch();
//! Step toward the neighbour with the highest value minus step cost. strict requires
//! real progress; otherwise a random sidestep to an equal cell is accepted when blocked.
bool directionByGradient(Uint32 teamMask, int swimClass, int x, int y, const Uint16 *gradient, int *dx, int *dy, bool strict) const;
Expand Down Expand Up @@ -729,6 +745,35 @@ class Map
// Used to attract idle workers into clearing
// areas that aren't clear
Uint16 *clearAreasGradient[Team::MAX_COUNT][SWIM_CLASS_COUNT];

// Round-robin fields in flight. A job is seeded at tick T into its own buffer,
// propagated by the worker thread, and published (buffer swapped into the slot)
// at tick T + GRADIENT_PIPELINE_TICKS. The slot keeps its previous buffer
// readable until then; afterwards that buffer becomes a spare for a later job.
struct GradientJob
{
Uint16 **slot = NULL;
Uint16 *buffer = NULL;
int swimClass = 0;
Uint32 publishTick = 0;
bool done = false;
};
std::deque<GradientJob> gradientJobs;
std::vector<Uint16 *> spareGradients;
std::thread gradientWorker;
std::mutex gradientMutex;
std::condition_variable gradientWake;
bool gradientWorkerQuit = false;
//! Pick and seed the next field of the round robin into job.buffer; false if none is in use.
bool pickRoundRobinField(GradientJob &job);
void startGradientWorker();
//! Publish every job due at or before tick now, waiting for the worker if needed.
void publishGradients(Uint32 now);
//! Publish every job in flight and stop the worker (before freeing gradients).
void finishPendingGradients();
void seedResourcesGradient(int teamNumber, Uint8 resourceType, int swimClass, Uint16 *gradient);
void seedGuardAreasGradient(int teamNumber, int swimClass, Uint16 *gradient);
void seedClearAreasGradient(int teamNumber, int swimClass, Uint16 *gradient);

public:
// Used to guide explorers
Expand Down
145 changes: 112 additions & 33 deletions src/map/MapStep.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#endif // !YOG_SERVER_ONLY

#include <algorithm>
#include <thread>


// growResources, syncStep, fog of war, discovery, explored area
Expand Down Expand Up @@ -92,50 +93,128 @@ void Map::syncStep(Uint32 stepCounter)
if (team < game->mapHeader.getNumberOfTeams())
updateExploredArea(team);
}

// We only update one gradient per step, round robin over the gradients in use:
bool updated=false;
while (!updated)
}

namespace {

// Ticks between seeding a round-robin field and publishing it. The worker has
// this long to propagate it; the units read the previous field meanwhile. Part of
// the simulation: every peer must use the same value.
constexpr Uint32 GRADIENT_PIPELINE_TICKS = 3;

} // namespace

void Map::startGradientWorker()
{
if (gradientWorker.joinable())
return;
gradientWorkerQuit = false;
gradientWorker = std::thread([this]
{
int numberOfTeam=game->mapHeader.getNumberOfTeams();
for (int t=0; t<numberOfTeam; t++)
for (int r=0; r<MAX_RESOURCES; r++)
for (int s=0; s<SWIM_CLASS_COUNT; s++)
GradientScratchPtr scratch = newGradientScratch();
std::unique_lock<std::mutex> lock(gradientMutex);
for (;;)
{
// Jobs are propagated in order; the first undone one is ours.
GradientJob *job = NULL;
gradientWake.wait(lock, [this, &job]
{
if (gradientWorkerQuit)
return true;
for (GradientJob &j : gradientJobs)
if (!j.done)
{
job = &j;
return true;
}
return false;
});
if (gradientWorkerQuit)
return;
Uint16 *buffer = job->buffer;
int swimClass = job->swimClass;
lock.unlock();
propagateGradient(buffer, swimClass, *scratch);
lock.lock();
// publishGradients pops only done jobs from the front and waits on this
// one, so it is still in the deque; find it again by buffer in case the
// deque reallocated while we were unlocked.
for (GradientJob &j : gradientJobs)
if (j.buffer == buffer)
j.done = true;
gradientWake.notify_all();
}
});
}

void Map::stepGradients()
{
const Uint32 now = game->stepCounter;
publishGradients(now);
if (spareGradients.empty())
spareGradients.push_back(new Uint16[size]);
GradientJob job;
job.buffer = spareGradients.back();
if (!pickRoundRobinField(job))
return;
spareGradients.pop_back();
job.publishTick = now + GRADIENT_PIPELINE_TICKS;
startGradientWorker();
std::lock_guard<std::mutex> lock(gradientMutex);
gradientJobs.push_back(job);
gradientWake.notify_all();
}

bool Map::pickRoundRobinField(GradientJob &p)
{
const int numberOfTeam = game->mapHeader.getNumberOfTeams();
for (int pass = 0; pass < 2; pass++)
{
for (int t = 0; t < numberOfTeam; t++)
for (int r = 0; r < MAX_RESOURCES; r++)
for (int s = 0; s < SWIM_CLASS_COUNT; s++)
if (resourcesGradient[t][r][s] && !gradientUpdated[t][r][s])
{
updateResourcesGradient(t, r, s);
gradientUpdated[t][r][s]=true;
return;
gradientUpdated[t][r][s] = true;
p.slot = &resourcesGradient[t][r][s];
p.swimClass = s;
seedResourcesGradient(t, r, s, p.buffer);
return true;
}
for (int t=0; t<numberOfTeam; t++)
for(int s=0; s<SWIM_CLASS_COUNT; s++)
if(guardAreasGradient[t][s] && !guardGradientUpdated[t][s])
for (int t = 0; t < numberOfTeam; t++)
for (int s = 0; s < SWIM_CLASS_COUNT; s++)
if (guardAreasGradient[t][s] && !guardGradientUpdated[t][s])
{
updateGuardAreasGradient(t, s);
guardGradientUpdated[t][s]=true;
return;
guardGradientUpdated[t][s] = true;
p.slot = &guardAreasGradient[t][s];
p.swimClass = s;
seedGuardAreasGradient(t, s, p.buffer);
return true;
}
for (int t=0; t<numberOfTeam; t++)
for(int s=0; s<SWIM_CLASS_COUNT; s++)
if(clearAreasGradient[t][s] && !clearGradientUpdated[t][s])
for (int t = 0; t < numberOfTeam; t++)
for (int s = 0; s < SWIM_CLASS_COUNT; s++)
if (clearAreasGradient[t][s] && !clearGradientUpdated[t][s])
{
updateClearAreasGradient(t, s);
clearGradientUpdated[t][s]=true;
return;
clearGradientUpdated[t][s] = true;
p.slot = &clearAreasGradient[t][s];
p.swimClass = s;
seedClearAreasGradient(t, s, p.buffer);
return true;
}


for (int t=0; t<numberOfTeam; t++)
for (int r=0; r<MAX_RESOURCES; r++)
for (int s=0; s<SWIM_CLASS_COUNT; s++)
gradientUpdated[t][r][s]=false;
for (int t=0; t<numberOfTeam; t++)
for(int s=0; s<SWIM_CLASS_COUNT; s++)
// A full rotation is done: start the next one.
for (int t = 0; t < numberOfTeam; t++)
{
for (int r = 0; r < MAX_RESOURCES; r++)
for (int s = 0; s < SWIM_CLASS_COUNT; s++)
gradientUpdated[t][r][s] = false;
for (int s = 0; s < SWIM_CLASS_COUNT; s++)
{
guardGradientUpdated[t][s]=false;
clearGradientUpdated[t][s]=false;
guardGradientUpdated[t][s] = false;
clearGradientUpdated[t][s] = false;
}
}
}
return false;
}
#endif // !YOG_SERVER_ONLY

Expand Down
14 changes: 12 additions & 2 deletions src/map/gradient/MapGradientArea.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ void Map::updateForbiddenGradient()
void Map::updateGuardAreasGradient(int teamNumber, int swimClass)
{
Uint16 *gradient = guardAreasGradient[teamNumber][swimClass];
seedGuardAreasGradient(teamNumber, swimClass, gradient);
propagateGradient(gradient, swimClass);
}

void Map::seedGuardAreasGradient(int teamNumber, int swimClass, Uint16 *gradient)
{
assert(gradient);
bool canSwim = swimClass > 0;

Expand All @@ -134,7 +140,6 @@ void Map::updateGuardAreasGradient(int teamNumber, int swimClass)
gradient[i] = GRADIENT_UNREACHABLE;
}

propagateGradient(gradient, swimClass);
}

void Map::updateGuardAreasGradient(int teamNumber)
Expand All @@ -154,6 +159,12 @@ void Map::updateGuardAreasGradient()
void Map::updateClearAreasGradient(int teamNumber, int swimClass)
{
Uint16 *gradient = clearAreasGradient[teamNumber][swimClass];
seedClearAreasGradient(teamNumber, swimClass, gradient);
propagateGradient(gradient, swimClass);
}

void Map::seedClearAreasGradient(int teamNumber, int swimClass, Uint16 *gradient)
{
assert(gradient);
bool canSwim = swimClass > 0;

Expand All @@ -177,7 +188,6 @@ void Map::updateClearAreasGradient(int teamNumber, int swimClass)
gradient[i] = GRADIENT_UNREACHABLE;
}

propagateGradient(gradient, swimClass);
}

void Map::updateClearAreasGradient(int teamNumber)
Expand Down
Loading
Loading