all repos — mgba @ 253719d7a14e0002c8e987beff4c6a6a3604704f

mGBA Game Boy Advance Emulator

src/core/rewind.c (view raw)

  1/* Copyright (c) 2013-2016 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include <mgba/core/rewind.h>
  7
  8#include <mgba/core/core.h>
  9#include <mgba/core/serialize.h>
 10#include <mgba-util/patch/fast.h>
 11#include <mgba-util/vfs.h>
 12
 13DEFINE_VECTOR(mCoreRewindPatches, struct PatchFast);
 14
 15void _rewindDiff(struct mCoreRewindContext* context);
 16
 17#ifndef DISABLE_THREADING
 18THREAD_ENTRY _rewindThread(void* context);
 19#endif
 20
 21void mCoreRewindContextInit(struct mCoreRewindContext* context, size_t entries, bool onThread) {
 22	mCoreRewindPatchesInit(&context->patchMemory, entries);
 23	size_t e;
 24	for (e = 0; e < entries; ++e) {
 25		initPatchFast(mCoreRewindPatchesAppend(&context->patchMemory));
 26	}
 27	context->previousState = VFileMemChunk(0, 0);
 28	context->currentState = VFileMemChunk(0, 0);
 29	context->size = 0;
 30	context->stateFlags = SAVESTATE_SAVEDATA;
 31#ifndef DISABLE_THREADING
 32	context->onThread = onThread;
 33	if (onThread) {
 34		MutexInit(&context->mutex);
 35		ConditionInit(&context->cond);
 36		ThreadCreate(&context->thread, _rewindThread, context);
 37	}
 38#else
 39	UNUSED(onThread);
 40#endif
 41}
 42
 43void mCoreRewindContextDeinit(struct mCoreRewindContext* context) {
 44#ifndef DISABLE_THREADING
 45	if (context->onThread) {
 46		MutexLock(&context->mutex);
 47		context->onThread = false;
 48		MutexUnlock(&context->mutex);
 49		ConditionWake(&context->cond);
 50		ThreadJoin(context->thread);
 51		MutexDeinit(&context->mutex);
 52		ConditionDeinit(&context->cond);
 53	}
 54#endif
 55	context->previousState->close(context->previousState);
 56	context->currentState->close(context->currentState);
 57	size_t s;
 58	for (s = 0; s < mCoreRewindPatchesSize(&context->patchMemory); ++s) {
 59		deinitPatchFast(mCoreRewindPatchesGetPointer(&context->patchMemory, s));
 60	}
 61	mCoreRewindPatchesDeinit(&context->patchMemory);
 62}
 63
 64void mCoreRewindAppend(struct mCoreRewindContext* context, struct mCore* core) {
 65#ifndef DISABLE_THREADING
 66	if (context->onThread) {
 67		MutexLock(&context->mutex);
 68	}
 69#endif
 70	struct VFile* nextState = context->previousState;
 71	mCoreSaveStateNamed(core, nextState, context->stateFlags);
 72	context->previousState = context->currentState;
 73	context->currentState = nextState;
 74#ifndef DISABLE_THREADING
 75	if (context->onThread) {
 76		ConditionWake(&context->cond);
 77		MutexUnlock(&context->mutex);
 78		return;
 79	}
 80#endif
 81	_rewindDiff(context);
 82}
 83
 84void _rewindDiff(struct mCoreRewindContext* context) {
 85	++context->current;
 86	if (context->size < mCoreRewindPatchesSize(&context->patchMemory)) {
 87		++context->size;
 88	}
 89	if (context->current >= mCoreRewindPatchesSize(&context->patchMemory)) {
 90		context->current = 0;
 91	}
 92	struct PatchFast* patch = mCoreRewindPatchesGetPointer(&context->patchMemory, context->current);
 93	size_t size2 = context->currentState->size(context->currentState);
 94	size_t size = context->previousState->size(context->previousState);
 95	if (size2 > size) {
 96		context->previousState->truncate(context->previousState, size2);
 97		size = size2;
 98	} else if (size > size2) {
 99		context->currentState->truncate(context->currentState, size);
100	}
101	void* current = context->previousState->map(context->previousState, size, MAP_READ);
102	void* next = context->currentState->map(context->currentState, size, MAP_READ);
103	diffPatchFast(patch, current, next, size);
104	context->previousState->unmap(context->previousState, current, size);
105	context->currentState->unmap(context->currentState, next, size);
106}
107
108bool mCoreRewindRestore(struct mCoreRewindContext* context, struct mCore* core) {
109#ifndef DISABLE_THREADING
110	if (context->onThread) {
111		MutexLock(&context->mutex);
112	}
113#endif
114	if (!context->size) {
115#ifndef DISABLE_THREADING
116		if (context->onThread) {
117			MutexUnlock(&context->mutex);
118		}
119#endif
120		return false;
121	}
122	--context->size;
123
124	struct PatchFast* patch = mCoreRewindPatchesGetPointer(&context->patchMemory, context->current);
125	size_t size2 = context->previousState->size(context->previousState);
126	size_t size = context->currentState->size(context->currentState);
127	if (size2 < size) {
128		size = size2;
129	}
130	void* current = context->currentState->map(context->currentState, size, MAP_READ);
131	void* previous = context->previousState->map(context->previousState, size, MAP_WRITE);
132	patch->d.applyPatch(&patch->d, current, size, previous, size);
133	context->currentState->unmap(context->currentState, current, size);
134	context->previousState->unmap(context->previousState, previous, size);
135	mCoreLoadStateNamed(core, context->previousState, context->stateFlags);
136	struct VFile* nextState = context->previousState;
137	context->previousState = context->currentState;
138	context->currentState = nextState;
139
140	if (context->current == 0) {
141		context->current = mCoreRewindPatchesSize(&context->patchMemory);
142	} 
143	--context->current;
144#ifndef DISABLE_THREADING
145	if (context->onThread) {
146		MutexUnlock(&context->mutex);
147	}
148#endif
149	return true;
150}
151
152#ifndef DISABLE_THREADING
153THREAD_ENTRY _rewindThread(void* context) {
154	struct mCoreRewindContext* rewindContext = context;
155	ThreadSetName("Rewind Diff Thread");
156	MutexLock(&rewindContext->mutex);
157	struct VFile* state = rewindContext->currentState;
158	while (rewindContext->onThread) {
159		if (rewindContext->currentState != state) {
160			_rewindDiff(rewindContext);
161			state = rewindContext->currentState;
162		}
163		ConditionWait(&rewindContext->cond, &rewindContext->mutex);
164	}
165	MutexUnlock(&rewindContext->mutex);
166	return 0;
167}
168#endif
169