Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved replication #116

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
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
137 changes: 137 additions & 0 deletions src/multiplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,142 @@
#include "engine.h"
#include "multiplayer_internal.h"

namespace replication
{
Item::Item(ControlBlock& control, const Settings& settings)
: controller{ control }
{
controller.add(*this, settings);
}

Item::~Item() = default;

void Item::setId(Key, uint16_t id)
{
replication_id = id;
}

ControlBlock& Item::getController(Key) const
{
return controller;
}

uint16_t Item::getId(Key) const
{
return replication_id;
}

void Item::setDirty()
{
controller.setDirty(*this);
}

void ControlBlock::add(Item& item, const Item::Settings& settings)
{
assert(items.size() < static_cast<size_t>(~controlled_flag));
item.setId({}, static_cast<uint16_t>(items.size()));
items.emplace_back(&item);
min_update_interval.emplace_back(settings.min_delay);
time_since_last_update.emplace_back(0.f);
if (dirty.size() * sizeof(size_t) < items.size())
dirty.resize((items.size() + sizeof(size_t) - 1) / sizeof(size_t));

#ifdef DEBUG
names.emplace_back(settings.name);
#endif
}

bool ControlBlock::isDirty(const Item& item) const
{
assert(&item.getController({}) == this);
return isDirty(item.getId({}));
}

void ControlBlock::setDirty(const Item& item)
{
assert(&item.getController({}) == this);
setDirty(item.getId({}));
}

size_t ControlBlock::send(sf::Packet& packet, bool everything, float delta /* = 0.f */)
{
size_t sent{};

// Update all deltas.
for (auto& value : time_since_last_update)
value += delta;

if (!everything)
{
for (auto dirty_batch = 0; dirty_batch < dirty.size(); ++dirty_batch)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I like this. My method with the single direction linked list was simpler and more efficient, compared to these packed dirty bits.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about yours being more efficient: linked lists involve a ton of pointer chasing which are rarely a prefetcher's favorite, and it required an extra pointer on the replicated members themselves, so the memory footprint was also higher as well.

However, again, measurement is king.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point on being more efficient is, the head pointer will usually point to nullptr, as any configuration value isn't changed, meaning we check nothing at all. And doing nothing is most certainly faster then checking an array to do nothing. I agree that going over the linked list is less efficient if it is linked, but it usually isn't...

And updating the 2 pointers should be in the same ballpark as looking up the right bit to set in the dirty array.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point on being more efficient is, the head pointer will usually point to nullptr

Interesting.
In my tests, where I converted a few of the fields on the spaceship, there's almost always 'something' that changed among all the replicated values, so from what I've it looked like the list would never really be empty...
And I figured it'd get worse once alll the fields would be converted.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, see where our difference is then, you want to convert all the fields, I just want to convert the ones that almost never change.

{
auto batch = dirty[dirty_batch];
if (batch) // skip over entire batches with no change.
{
for (auto i = 0; i < sizeof(size_t); ++i)
{
auto bit = (size_t{ 1 } << i);
auto index = dirty_batch * sizeof(size_t) + i;
if ((batch & bit) != 0 && time_since_last_update[index] > min_update_interval[index])
{
auto item = items[index];
packet << int16_t(item->getId({}) | controlled_flag);
item->send(*this, packet);
time_since_last_update[index] = 0.f;
sent += 1;
batch &= ~bit;
}
}

if (batch != dirty[dirty_batch])
{
dirty[dirty_batch] = batch;
}
}
}
}
else
{
for (auto& item : items)
{
packet << int16_t(item->getId({}) | controlled_flag);
item->send(*this, packet);
}
sent = items.size();
}

return sent;
}

bool ControlBlock::handles(uint16_t net_id) const
{
return (net_id & controlled_flag) && (net_id & ~controlled_flag) < items.size();
}

void ControlBlock::receive(uint16_t net_id, sf::Packet& packet)
{
auto element_key = net_id & ~controlled_flag;
items[element_key]->receive(*this, packet);
}


bool ControlBlock::isDirty(size_t id) const
{
return (dirty[id / sizeof(size_t)] & (size_t{ 1 } << (id % sizeof(size_t)))) != 0;
}

void ControlBlock::setDirty(size_t id)
{
dirty[id / sizeof(size_t)] |= (size_t{ 1 } << (id % sizeof(size_t)));
}

void ControlBlock::resetDirty()
{
memset(dirty.data(), 0, dirty.size() * sizeof(size_t));
}
} // ns replication


static PVector<Collisionable> collisionable_significant;
class CollisionableReplicationData
{
Expand All @@ -26,6 +162,7 @@ MultiplayerClassListItem* multiplayerClassListStart;

MultiplayerObject::MultiplayerObject(string multiplayerClassIdentifier)
: multiplayerClassIdentifier(multiplayerClassIdentifier)
, replicationControl{std::make_unique<replication::ControlBlock>()}
{
multiplayerObjectId = noId;
replicated = false;
Expand Down
Loading