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