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