// Copyright (c) 2023 Alex Diener. All rights reserved. #include "PROJECT_NAME/RoomState.h" #include "PROJECT_NAME/RoomStateUndoStateDelta.h" #include "PROJECT_NAME/Utilities.h" #include "utilities/UndoUtilities.h" #define stemobject_implementation RoomStateUndoStateDelta stemobject_vtable_begin(); stemobject_vtable_entry(dispose); stemobject_vtable_entry(revert); stemobject_vtable_entry(apply); stemobject_vtable_entry(copy); stemobject_vtable_end(); typedef enum UndoStateDelta_changeType { CHANGE_TYPE_ADD, CHANGE_TYPE_REMOVE, CHANGE_TYPE_MODIFY } UndoStateDelta_changeType; struct RoomStateUndoStateDelta_entityChange { UndoStateDelta_changeType type; unsigned int index; struct GameEntity_vtableStruct * vtable; size_t size; char data[1]; }; RoomStateUndoStateDelta * RoomStateUndoStateDelta_create(RoomState * roomState, RoomStateUndoData * undoData) { stemobject_create_implementation(init, roomState, undoData) } static bool compareEntities(void * oldItem, void * newItem, unsigned int * outCompareResult, void * context) { struct RoomState_entity * oldRoomEntity = oldItem; struct RoomState_entity * newRoomEntity = newItem; GameEntity * oldEntity = oldRoomEntity->entity; GameEntity * newEntity = newRoomEntity->entity; if (oldEntity->entityID != newEntity->entityID) { return false; } *outCompareResult = !call_virtual(isEqual, oldEntity, newEntity); return true; } struct encodeChangeContext { ChunkArray * array; UndoStateDelta_changeType type; unsigned int index; }; struct enumerateChangesContext { RoomStateUndoStateDelta * self; struct memwriteContext memwriteContext1; struct memwriteContext memwriteContext2; }; static void addEntityChange(RoomStateUndoStateDelta * self, UndoStateDelta_changeType type, unsigned int index, struct GameEntity_vtableStruct * vtable, struct memwriteContext * memwriteContext) { struct RoomStateUndoStateDelta_entityChange * change = ChunkArray_append(&self->entityChanges, NULL, sizeof(*change) - sizeof(change->data) + memwriteContext->length); change->type = type; change->index = index; change->vtable = vtable; change->size = memwriteContext->length; memcpy(change->data, memwriteContext->data, memwriteContext->length); memwriteContext->length = memwriteContext->position = 0; } static void entityAddedCallback(unsigned int index, void * newItem, void * context) { struct enumerateChangesContext * contextStruct = context; RoomStateUndoStateDelta * self = contextStruct->self; struct RoomState_entity * roomEntity = newItem; call_virtual(encodeState, roomEntity->entity, &contextStruct->memwriteContext1, false); addEntityChange(self, CHANGE_TYPE_ADD, index, roomEntity->entity->vtable, &contextStruct->memwriteContext1); } static void entityRemovedCallback(unsigned int index, void * oldItem, void * context) { struct enumerateChangesContext * contextStruct = context; RoomStateUndoStateDelta * self = contextStruct->self; struct RoomState_entity * roomEntity = oldItem; call_virtual(encodeState, roomEntity->entity, &contextStruct->memwriteContext1, false); addEntityChange(self, CHANGE_TYPE_REMOVE, index, roomEntity->entity->vtable, &contextStruct->memwriteContext1); } static void entityModifiedCallback(unsigned int index, void * oldItem, void * newItem, unsigned int compareResult, void * context) { struct enumerateChangesContext * contextStruct = context; RoomStateUndoStateDelta * self = contextStruct->self; struct RoomState_entity * oldRoomEntity = oldItem; struct RoomState_entity * newRoomEntity = newItem; call_virtual(encodeState, oldRoomEntity->entity, &contextStruct->memwriteContext1, true); call_virtual(encodeState, newRoomEntity->entity, &contextStruct->memwriteContext2, true); memxor(contextStruct->memwriteContext1.data, contextStruct->memwriteContext2.data, contextStruct->memwriteContext1.length); contextStruct->memwriteContext2.length = contextStruct->memwriteContext2.position = 0; addEntityChange(self, CHANGE_TYPE_MODIFY, index, oldRoomEntity->entity->vtable, &contextStruct->memwriteContext1); } bool RoomStateUndoStateDelta_init(RoomStateUndoStateDelta * self, RoomState * roomState, RoomStateUndoData * undoData) { call_super(init, self); self->entityChanges = ChunkArray_init(); struct enumerateChangesContext contextStruct = {self, memwriteContextInit(NULL, 0, 0, true), memwriteContextInit(NULL, 0, 0, true)}; enumerateIdentifiableObjectChanges(undoData->entities, undoData->entityCount, roomState->entities, roomState->entityCount, sizeof(*roomState->entities), compareEntities, entityAddedCallback, entityRemovedCallback, entityModifiedCallback, &contextStruct); free(contextStruct.memwriteContext1.data); free(contextStruct.memwriteContext2.data); return true; } void RoomStateUndoStateDelta_dispose(RoomStateUndoStateDelta * self) { ChunkArray_free(&self->entityChanges); call_super_virtual(dispose, self); } static void applyChange(RoomState * roomState, struct RoomStateUndoStateDelta_entityChange * change, UndoStateDelta_changeType addChangeType, UndoStateDelta_changeType removeChangeType, GameEntity ** createdEntities, unsigned int * ioCreatedEntityCount) { if (change->type == addChangeType) { struct memreadContext memreadContext = memreadContextInit(change->data, change->size); GameEntity * entity = change->vtable->createWithEncodedState(&memreadContext, roomState); RoomState_insertEntity(roomState, change->index, entity, true); createdEntities[*ioCreatedEntityCount] = entity; ++*ioCreatedEntityCount; } else if (change->type == removeChangeType) { RoomState_removeEntityAtIndex(roomState, change->index); } else { struct memreadContext memreadContext = memreadContextInit(change->data, change->size); call_virtual(xorState, roomState->entities[change->index].entity, &memreadContext); } } void RoomStateUndoStateDelta_revert(RoomStateUndoStateDelta * self, void * target) { GameEntity * createdEntities[self->entityChanges.count]; unsigned int createdEntityCount = 0; for (unsigned int changeIndex = self->entityChanges.count - 1; changeIndex < self->entityChanges.count; changeIndex--) { struct RoomStateUndoStateDelta_entityChange * change = ChunkArray_entryAtIndex(&self->entityChanges, changeIndex); applyChange(target, change, CHANGE_TYPE_REMOVE, CHANGE_TYPE_ADD, createdEntities, &createdEntityCount); } for (unsigned int entityIndex = 0; entityIndex < createdEntityCount; entityIndex++) { call_virtual(postinit, createdEntities[entityIndex]); } } void RoomStateUndoStateDelta_apply(RoomStateUndoStateDelta * self, void * target) { GameEntity * createdEntities[self->entityChanges.count]; unsigned int createdEntityCount = 0; for (unsigned int changeIndex = 0; changeIndex < self->entityChanges.count; changeIndex++) { struct RoomStateUndoStateDelta_entityChange * change = ChunkArray_entryAtIndex(&self->entityChanges, changeIndex); applyChange(target, change, CHANGE_TYPE_ADD, CHANGE_TYPE_REMOVE, createdEntities, &createdEntityCount); } for (unsigned int entityIndex = 0; entityIndex < createdEntityCount; entityIndex++) { call_virtual(postinit, createdEntities[entityIndex]); } } static void RoomStateUndoStateDelta_initCopy(RoomStateUndoStateDelta * self, RoomStateUndoStateDelta * original) { self->entityChanges = ChunkArray_copy(&original->entityChanges); } RoomStateUndoStateDelta * RoomStateUndoStateDelta_copy(RoomStateUndoStateDelta * self) { stemobject_copy_implementation(initCopy) }