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