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