all repos — mgba @ 893fdd383f3f4f59de31ac617bc8a28d3f8059c6

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	context->ready = false;
 34	if (onThread) {
 35		MutexInit(&context->mutex);
 36		ConditionInit(&context->cond);
 37		ThreadCreate(&context->thread, _rewindThread, context);
 38	}
 39#else
 40	UNUSED(onThread);
 41#endif
 42}
 43
 44void mCoreRewindContextDeinit(struct mCoreRewindContext* context) {
 45#ifndef DISABLE_THREADING
 46	if (context->onThread) {
 47		MutexLock(&context->mutex);
 48		context->onThread = false;
 49		MutexUnlock(&context->mutex);
 50		ConditionWake(&context->cond);
 51		ThreadJoin(context->thread);
 52		MutexDeinit(&context->mutex);
 53		ConditionDeinit(&context->cond);
 54	}
 55#endif
 56	context->previousState->close(context->previousState);
 57	context->currentState->close(context->currentState);
 58	size_t s;
 59	for (s = 0; s < mCoreRewindPatchesSize(&context->patchMemory); ++s) {
 60		deinitPatchFast(mCoreRewindPatchesGetPointer(&context->patchMemory, s));
 61	}
 62	mCoreRewindPatchesDeinit(&context->patchMemory);
 63}
 64
 65void mCoreRewindAppend(struct mCoreRewindContext* context, struct mCore* core) {
 66#ifndef DISABLE_THREADING
 67	if (context->onThread) {
 68		MutexLock(&context->mutex);
 69	}
 70#endif
 71	struct VFile* nextState = context->previousState;
 72	mCoreSaveStateNamed(core, nextState, context->stateFlags);
 73	context->previousState = context->currentState;
 74	context->currentState = nextState;
 75#ifndef DISABLE_THREADING
 76	if (context->onThread) {
 77		context->ready = true;
 78		ConditionWake(&context->cond);
 79		MutexUnlock(&context->mutex);
 80		return;
 81	}
 82#endif
 83	_rewindDiff(context);
 84}
 85
 86void _rewindDiff(struct mCoreRewindContext* context) {
 87	++context->current;
 88	if (context->size < mCoreRewindPatchesSize(&context->patchMemory)) {
 89		++context->size;
 90	}
 91	if (context->current >= mCoreRewindPatchesSize(&context->patchMemory)) {
 92		context->current = 0;
 93	}
 94	struct PatchFast* patch = mCoreRewindPatchesGetPointer(&context->patchMemory, context->current);
 95	size_t size2 = context->currentState->size(context->currentState);
 96	size_t size = context->previousState->size(context->previousState);
 97	if (size2 > size) {
 98		context->previousState->truncate(context->previousState, size2);
 99		size = size2;
100	} else if (size > size2) {
101		context->currentState->truncate(context->currentState, size);
102	}
103	void* current = context->previousState->map(context->previousState, size, MAP_READ);
104	void* next = context->currentState->map(context->currentState, size, MAP_READ);
105	diffPatchFast(patch, current, next, size);
106	context->previousState->unmap(context->previousState, current, size);
107	context->currentState->unmap(context->currentState, next, size);
108}
109
110bool mCoreRewindRestore(struct mCoreRewindContext* context, struct mCore* core) {
111#ifndef DISABLE_THREADING
112	if (context->onThread) {
113		MutexLock(&context->mutex);
114	}
115#endif
116	if (!context->size) {
117#ifndef DISABLE_THREADING
118		if (context->onThread) {
119			MutexUnlock(&context->mutex);
120		}
121#endif
122		return false;
123	}
124	--context->size;
125
126	mCoreLoadStateNamed(core, context->previousState, context->stateFlags);
127	if (context->current == 0) {
128		context->current = mCoreRewindPatchesSize(&context->patchMemory);
129	}
130	--context->current;
131
132	struct PatchFast* patch = mCoreRewindPatchesGetPointer(&context->patchMemory, context->current);
133	size_t size2 = context->previousState->size(context->previousState);
134	size_t size = context->currentState->size(context->currentState);
135	if (size2 < size) {
136		size = size2;
137	}
138	void* current = context->currentState->map(context->currentState, size, MAP_READ);
139	void* previous = context->previousState->map(context->previousState, size, MAP_WRITE);
140	patch->d.applyPatch(&patch->d, previous, size, current, size);
141	context->currentState->unmap(context->currentState, current, size);
142	context->previousState->unmap(context->previousState, previous, size);
143	struct VFile* nextState = context->previousState;
144	context->previousState = context->currentState;
145	context->currentState = nextState;
146#ifndef DISABLE_THREADING
147	if (context->onThread) {
148		MutexUnlock(&context->mutex);
149	}
150#endif
151	return true;
152}
153
154#ifndef DISABLE_THREADING
155THREAD_ENTRY _rewindThread(void* context) {
156	struct mCoreRewindContext* rewindContext = context;
157	ThreadSetName("Rewind Diff Thread");
158	MutexLock(&rewindContext->mutex);
159	while (rewindContext->onThread) {
160		while (!rewindContext->ready && rewindContext->onThread) {
161			ConditionWait(&rewindContext->cond, &rewindContext->mutex);
162		}
163		if (rewindContext->ready) {
164			_rewindDiff(rewindContext);
165		}
166		rewindContext->ready = false;
167	}
168	MutexUnlock(&rewindContext->mutex);
169	return 0;
170}
171#endif
172