455 lines
14 KiB
C++
455 lines
14 KiB
C++
#include <ZSimulation/ZPropertyBuffer.hpp>
|
|
#include <ZSimulation/ZNetworkUpdateStream.hpp>
|
|
|
|
#include <SST/SST_SysMem.h>
|
|
|
|
#define BUFFER_SYNC 0
|
|
#define BUFFER_LOCAL 1
|
|
#define BUFFER_WRITE 0
|
|
#define BUFFER_READ 1
|
|
|
|
#define MIN_ALLOC 4
|
|
|
|
struct PropertyPage {
|
|
eID Id; // id of the entity that these properties are allocated for
|
|
void* Data[2]; // the data for this page (read and write)
|
|
size_t Size; // amount of data on this page
|
|
size_t Offset; // the current offset on this page
|
|
PropertyPage* Next; // the next page, or NULL
|
|
|
|
ZArray<ZPair<size_t, size_t>> Avail; // available data slots on this page
|
|
ZArray<ZPair<ZPropertyBuffer::PropertyKey, size_t>> Updates; // properties that have been updated since last update
|
|
|
|
// c'tor
|
|
PropertyPage(size_t alloc_size) : Next(NULL) {
|
|
Data[0] = SST_OS_AllocPages(alloc_size*2); // allocate 2x to account for read and write
|
|
Data[1] = (char*)Data[0] + alloc_size; // split in half, with first half being write and second for read
|
|
Size = alloc_size;
|
|
Offset = 0;
|
|
}
|
|
|
|
// d'tor
|
|
~PropertyPage() {
|
|
delete Next;
|
|
if (Size > 0) {
|
|
SST_OS_FreePages(Data[0], Size*2);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*************************************************************************/
|
|
|
|
// Key is currently a 64 bit integer, which is partitioned as follows
|
|
//
|
|
// [ SYNC MOE (2) | OFFSET (14) | SUB PAGE (16) | PAGE (32) ]
|
|
|
|
static ZPropertyBuffer::PropertyKey GenerateKey(ZPropertyBuffer::SyncMode mode, uint32_t page, uint16_t sub_page, uint16_t offset) {
|
|
|
|
uint64_t key;
|
|
|
|
key = mode;
|
|
key = key << 14;
|
|
key = key + offset;
|
|
key = key << 16;
|
|
key = key + sub_page;
|
|
key = key << 32;
|
|
key = key + page;
|
|
|
|
// dumb assert
|
|
static_assert(sizeof(eID) == 4, "Cannot generate property key with sizeof(eID) not 32 bits, update property buffer");
|
|
|
|
return key;
|
|
}
|
|
|
|
static void ParseKey(ZPropertyBuffer::PropertyKey key, ZPropertyBuffer::SyncMode* mode, uint32_t* page, uint16_t* sub_page, uint16_t* offset) {
|
|
*page = (uint32_t)(key & 0xFFFFFFFF);
|
|
*sub_page = (uint16_t)((key >> 32) & 0xFFFF);
|
|
*offset = (uint16_t)((key >> 48) & 0x3FFF);
|
|
*mode = (ZPropertyBuffer::SyncMode)((key >> 62) & 0xFFFF);
|
|
}
|
|
|
|
void* ZPropertyBuffer::GetData( PropertyKey key, int rw, PropertyPage** page_out /*= NULL*/, SyncMode* mode_out /*= NULL*/ )
|
|
{
|
|
SyncMode mode;
|
|
uint32_t page_idx;
|
|
uint16_t sub_page_idx, offset;
|
|
|
|
ParseKey(key, &mode, &page_idx, &sub_page_idx, &offset); // parse data key
|
|
|
|
ZArray<PropertyPage*>& pages = mode != SYNC_NONE ? Pages[BUFFER_SYNC] :
|
|
Pages[BUFFER_LOCAL]; // determine if we are using sync or local data
|
|
|
|
if (pages.Size() >= page_idx) { // determine if we have this many pages
|
|
PropertyPage* page = pages[page_idx];
|
|
|
|
for (size_t i = 0; i < sub_page_idx; i++) { // see of we can find the sub page
|
|
page = page->Next;
|
|
if (page == NULL) { // check for bad sub page index
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (offset % MIN_ALLOC == 0 && offset <= page->Size - MIN_ALLOC) { // see if our offset is valid
|
|
if (page_out != NULL) *page_out = page; // get pointer to page
|
|
if (mode_out != NULL) *mode_out = mode; // get sync value
|
|
return (char*)page->Data[rw] + offset; // get read / write pointer
|
|
} else return NULL;
|
|
} else return NULL;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZPropertyBuffer::Property ZPropertyBuffer::AllocData( PropertyPage* page, eID id, uint32_t page_idx, size_t size, SyncMode mode)
|
|
{
|
|
Property prop;
|
|
|
|
ZArray<PropertyPage*>& pages = mode ? Pages[BUFFER_SYNC] : Pages[BUFFER_LOCAL]; // determine if we are using sync or local data
|
|
|
|
size_t alloc_size = size > MIN_ALLOC ? (size + MIN_ALLOC - 1) & ~(MIN_ALLOC - 1) // ensure we allocate at least MIN_ALLOC, and align to MIN_ALLOC
|
|
: size;
|
|
|
|
uint16_t sub_page_idx;
|
|
|
|
for (sub_page_idx = 0; page != NULL; sub_page_idx++) { // loop pages seeing if we can drop the data in here
|
|
if (page->Offset + size <= page->Size) { // see if we have room
|
|
uint16_t offset = (uint16_t)page->Offset; // we do, so get current offset
|
|
page->Offset = page->Offset + alloc_size; // increment stored offset by alloc size, not property size
|
|
prop.Key = GenerateKey(mode, page_idx, sub_page_idx, offset); // key for this value
|
|
prop.Write = (char*)page->Data[BUFFER_WRITE] + offset; // write pointer for this value
|
|
prop.Read = (char*)page->Data[BUFFER_READ] + offset; // read pointer for this value
|
|
prop.Size = size; // size of this value
|
|
return prop;
|
|
} else {
|
|
for (size_t i = 0; i < page->Avail.Size(); i++) { // for each available slot from previous deallocs
|
|
if (page->Avail[i].Second >= size) { // see if we have room
|
|
uint16_t offset = (uint16_t)page->Avail[i].First; // we do, so store the offset
|
|
page->Avail.Erase(i); // no longer available
|
|
prop.Key = GenerateKey(mode, page_idx, sub_page_idx, offset); // key for this value
|
|
prop.Write = (char*)page->Data[BUFFER_WRITE] + offset; // write pointer for this value
|
|
prop.Read = (char*)page->Data[BUFFER_READ] + offset; // read pointer for this value
|
|
prop.Size = size; // size of this value
|
|
return prop;
|
|
}
|
|
}
|
|
|
|
if (page->Next != NULL) {
|
|
page = page->Next; // NEXT
|
|
} else {
|
|
sub_page_idx++; // sub page index will be next index
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// found nothing, time to allocate new
|
|
|
|
if (Pages[BUFFER_LOCAL].Size() <= page_idx) { // push NULL entry for local buffer if not present at page_idx
|
|
Pages[BUFFER_LOCAL].PushBack(NULL);
|
|
}
|
|
|
|
if (Pages[BUFFER_SYNC].Size() <= page_idx) { // push NULL entry for sync buffer if not present at page_idx
|
|
Pages[BUFFER_SYNC].PushBack(NULL);
|
|
}
|
|
|
|
const uint32_t page_size = SST_OS_GetPageSize(); // system page size
|
|
const uint32_t min_buffer_alloc = page_size / 2; // minimum amount we will allocate
|
|
|
|
uint32_t page_alloc_size = min_buffer_alloc; // starting page alloc
|
|
|
|
if (size > page_alloc_size) {
|
|
page_alloc_size =
|
|
((size+min_buffer_alloc-1) & ~(min_buffer_alloc-1)); // round up to nearest multiple of min_buffer_alloc
|
|
}
|
|
|
|
PropertyPage* npage = new PropertyPage(page_alloc_size); // new page
|
|
npage->Id = id; // set id for page
|
|
npage->Offset = alloc_size; // allocate enough space for the first property
|
|
|
|
if (page != NULL) { // link page into page list
|
|
page->Next = npage;
|
|
} else { // allocating new pages
|
|
pages[page_idx] = npage;
|
|
}
|
|
|
|
prop.Key = GenerateKey(mode, page_idx, sub_page_idx, 0); // generate a key for the correct page with sub-page and offset 0
|
|
prop.Write = (char*)npage->Data[BUFFER_WRITE]; // set the write pointer
|
|
prop.Read = (char*)npage->Data[BUFFER_READ]; // set the read pointer
|
|
prop.Size = size; // set the size of the data
|
|
return prop;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZPropertyBuffer::ZPropertyBuffer()
|
|
{
|
|
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZPropertyBuffer::~ZPropertyBuffer()
|
|
{
|
|
for (size_t i = 0; i < Pages[BUFFER_LOCAL].Size(); i++) {
|
|
delete Pages[BUFFER_LOCAL][i];
|
|
}
|
|
|
|
for (size_t i = 0; i < Pages[BUFFER_SYNC].Size(); i++) {
|
|
delete Pages[BUFFER_SYNC][i];
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
ZPropertyBuffer::Property ZPropertyBuffer::AllocProperty(eID id, size_t size, SyncMode mode)
|
|
{
|
|
// sanity check - synchronized properties should be less than ZSIM_BUFFER_SYNC_MAX
|
|
ZASSERT_RUNTIME(!mode || size < ZSIM_SYNCBUFFER_MAX, "Synchronized property allocation too large!");
|
|
|
|
auto itr = IdPageMap.Find(id);
|
|
|
|
ZArray<PropertyPage*>& pages = mode != SYNC_NONE ? Pages[BUFFER_SYNC] :
|
|
Pages[BUFFER_LOCAL];
|
|
|
|
if (itr != IdPageMap.End()) {
|
|
uint32_t page_idx = itr.GetValue();
|
|
PropertyPage* page = pages[page_idx];
|
|
|
|
return AllocData(page, id, page_idx, size, mode);
|
|
}
|
|
|
|
if (Avail.Size() > 0) {
|
|
size_t page_idx = Avail.PopBack();
|
|
IdPageMap.Put(id, page_idx);
|
|
return AllocData(NULL, id, page_idx, size, mode);
|
|
} else {
|
|
IdPageMap.Put(id, pages.Size());
|
|
return AllocData(NULL, id, pages.Size(), size, mode);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZPropertyBuffer::DeallocEntity( eID id )
|
|
{
|
|
auto itr = IdPageMap.Find(id);
|
|
|
|
if (itr != IdPageMap.End()) {
|
|
uint32_t page_idx = itr.GetValue();
|
|
|
|
delete Pages[BUFFER_LOCAL][page_idx];
|
|
delete Pages[BUFFER_SYNC][page_idx];
|
|
|
|
Pages[BUFFER_LOCAL][page_idx] = NULL;
|
|
Pages[BUFFER_SYNC][page_idx] = NULL;
|
|
|
|
Avail.PushBack(page_idx);
|
|
|
|
IdPageMap.Erase(id);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZPropertyBuffer::DeallocProperty( PropertyKey key, size_t size )
|
|
{
|
|
SyncMode mode;
|
|
uint32_t page_idx;
|
|
uint16_t sub_page_idx, offset;
|
|
|
|
ParseKey(key, &mode, &page_idx, &sub_page_idx, &offset);
|
|
|
|
ZArray<PropertyPage*>& pages = mode != SYNC_NONE ? Pages[BUFFER_SYNC] :
|
|
Pages[BUFFER_LOCAL];
|
|
|
|
PropertyPage* page = pages[page_idx];
|
|
|
|
for (size_t i = 0; i < sub_page_idx; i++) {
|
|
page = page->Next;
|
|
}
|
|
|
|
page->Avail.PushBack(ZPair<size_t, size_t>(offset, size));
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZPropertyBuffer::FlagModified( PropertyKey key, size_t size )
|
|
{
|
|
PropertyPage* page;
|
|
SyncMode mode;
|
|
|
|
void* data = GetData(key, BUFFER_WRITE, &page, &mode);
|
|
|
|
ZASSERT(data != NULL && page != NULL, "Attempt to flag modified on invalid property key");
|
|
|
|
page->Updates.PushBack(ZPair<PropertyKey, size_t>(key, size));
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZPropertyBuffer::UpdateModified(bool sync, ZNetworkUpdateStream& stream)
|
|
{
|
|
// wherever we have an update, copy the write buffer
|
|
for (size_t b = 0; b < 2; b++) {
|
|
for (size_t i = 0; i < Pages[b].Size(); i++) {
|
|
PropertyPage* page = Pages[b][i];
|
|
ZPropertyBufferUpdate* buffer_update = NULL;
|
|
|
|
// synchronized buffers require a property buffer update call
|
|
if (sync && b == BUFFER_SYNC && page != NULL && page->Updates.Size() > 0) {
|
|
buffer_update = stream.AllocUpdate(page->Id);
|
|
}
|
|
|
|
// for all pages and sub-pages
|
|
while (page != NULL) {
|
|
for (size_t j = 0; page != NULL && j < page->Updates.Size(); j++) {
|
|
PropertyKey key = page->Updates[j].First;
|
|
size_t size = page->Updates[j].Second;
|
|
|
|
SyncMode mode;
|
|
uint32_t page_idx;
|
|
uint16_t sub_page_idx, offset;
|
|
|
|
ParseKey(key, &mode, &page_idx, &sub_page_idx, &offset);
|
|
|
|
void* data = (char*)page->Data[BUFFER_WRITE] + offset;
|
|
MemCopyChar((char*)page->Data[BUFFER_READ] + offset, data, size);
|
|
|
|
// if needed, make it happen
|
|
if (buffer_update != NULL && mode == SYNC_SEND) {
|
|
ZPropertyBufferUpdate::Update update;
|
|
|
|
update.SubPage = sub_page_idx;
|
|
update.Offset = offset;
|
|
update.Size = size;
|
|
update.Data = (char*)page->Data[BUFFER_READ] + offset;
|
|
|
|
buffer_update->Updates.PushBack(update);
|
|
}
|
|
}
|
|
|
|
page->Updates.Clear();
|
|
page = page->Next;
|
|
}
|
|
|
|
// push the entity update
|
|
if (buffer_update != NULL) {
|
|
stream.EnqueueOutgoingUpdate(buffer_update);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
void ZPropertyBuffer::UpdateModified( eID id, bool sync, ZNetworkUpdateStream& stream )
|
|
{
|
|
auto itr = IdPageMap.Find(id);
|
|
|
|
if (itr != IdPageMap.End()) {
|
|
uint32_t page_idx = itr.GetValue();
|
|
|
|
// wherever we have an update, copy the write buffer
|
|
for (size_t b = BUFFER_LOCAL; b != BUFFER_SYNC; b = BUFFER_SYNC) {
|
|
PropertyPage* page = Pages[b][page_idx];
|
|
ZPropertyBufferUpdate* buffer_update = NULL;
|
|
|
|
// synchronized buffers require a property buffer update call
|
|
if (sync && b == BUFFER_SYNC && page != NULL) {
|
|
buffer_update = stream.AllocUpdate(id);
|
|
}
|
|
|
|
// for all pages and sub-pages
|
|
while (page != NULL) {
|
|
for (size_t j = 0; page != NULL && j < page->Updates.Size(); j++) {
|
|
PropertyKey key = page->Updates[j].First;
|
|
size_t size = page->Updates[j].Second;
|
|
|
|
SyncMode mode;
|
|
uint32_t page_idx;
|
|
uint16_t sub_page_idx, offset;
|
|
|
|
ParseKey(key, &mode, &page_idx, &sub_page_idx, &offset);
|
|
|
|
void* data = (char*)page->Data[BUFFER_WRITE] + offset;
|
|
MemCopyChar((char*)page->Data[BUFFER_READ] + offset, data, size);
|
|
|
|
if (buffer_update != NULL && mode == SYNC_SEND) {
|
|
ZPropertyBufferUpdate::Update update;
|
|
|
|
update.SubPage = sub_page_idx;
|
|
update.Offset = offset;
|
|
update.Size = size;
|
|
update.Data = (char*)page->Data[BUFFER_READ] + offset;
|
|
|
|
buffer_update->Updates.PushBack(update);
|
|
}
|
|
}
|
|
|
|
page->Updates.Clear();
|
|
page = page->Next;
|
|
}
|
|
|
|
// push the entity update
|
|
if (buffer_update != NULL) {
|
|
stream.EnqueueOutgoingUpdate(buffer_update);
|
|
}
|
|
}
|
|
} else {
|
|
SystemLogError("Unable to update entity property buffer - invalid id");
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static void* GetSyncData(PropertyPage* page, uint16_t offset)
|
|
{
|
|
if (offset % MIN_ALLOC == 0 && offset <= page->Size - MIN_ALLOC) { // see if our offset is valid
|
|
return (char*)page->Data[BUFFER_READ] + offset; // return read
|
|
} else return NULL;
|
|
}
|
|
|
|
void ZPropertyBuffer::ApplyBufferUpdates( const ZArray<ZPropertyBufferUpdate*>& updates )
|
|
{
|
|
for (size_t i = 0; i < updates.Size(); i++) {
|
|
ZPropertyBufferUpdate& buffer_update = *updates[i];
|
|
eID id = buffer_update.Id;
|
|
|
|
ZArray<PropertyPage*>& pages = Pages[BUFFER_SYNC]; // always sync data
|
|
|
|
auto itr = IdPageMap.Find(id); // find page mapped for id
|
|
|
|
if (itr != IdPageMap.End()) { // make sure we have a valid one
|
|
uint32_t page_idx = itr.GetValue();
|
|
PropertyPage* base_page = pages[page_idx];
|
|
|
|
for (size_t j = 0; j < buffer_update.Updates.Size(); j++) {
|
|
ZPropertyBufferUpdate::Update& update = buffer_update.Updates[j];
|
|
|
|
uint16_t sub_page_idx = update.SubPage;
|
|
uint16_t offset = update.Offset;
|
|
size_t size = update.Size;
|
|
|
|
PropertyPage* sub_page = base_page;
|
|
|
|
for (uint16_t k = 0; k < sub_page_idx; k++) {
|
|
if (sub_page->Next != NULL) {
|
|
sub_page = sub_page->Next;
|
|
}
|
|
}
|
|
|
|
if (sub_page != NULL) {
|
|
void* rd_ptr = GetSyncData(sub_page, offset);
|
|
|
|
if (rd_ptr != NULL) {
|
|
MemCopyChar(rd_ptr, update.Data, size);
|
|
} else {
|
|
SystemLogError("Recieved invalid buffer update offset");
|
|
}
|
|
} else {
|
|
SystemLogError("Recieved invalid buffer update sub page");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|