Pack movie data in memory
[lsnes.git] / src / core / movie.cpp
blobc16d6e16e6b0560e42f8e5b0db9fa2d5974e0899
1 #include "lsnes.hpp"
3 #include "core/misc.hpp"
4 #include "core/movie.hpp"
5 #include "core/rom.hpp"
7 #include <stdexcept>
8 #include <cassert>
9 #include <cstring>
10 #include <fstream>
13 //std::ofstream debuglog("movie-debugging-log", std::ios::out | std::ios::app);
15 namespace
17 bool movies_compatible(controller_frame_vector& old_movie, controller_frame_vector& new_movie,
18 uint64_t frame, const uint32_t* polls, const std::string& old_projectid,
19 const std::string& new_projectid)
21 //Project IDs have to match.
22 if(old_projectid != new_projectid)
23 return false;
24 //If new movie is before first frame, anything with same project_id is compatible.
25 if(frame == 0)
26 return true;
27 //Scan both movies until frame syncs are seen. Out of bounds reads behave as all neutral but frame
28 //sync done.
29 uint64_t syncs_seen = 0;
30 uint64_t frames_read = 0;
31 while(syncs_seen < frame - 1) {
32 controller_frame oldc = old_movie.blank_frame(true), newc = new_movie.blank_frame(true);
33 if(frames_read < old_movie.size())
34 oldc = old_movie[frames_read];
35 if(frames_read < new_movie.size())
36 newc = new_movie[frames_read];
37 if(oldc != newc)
38 return false; //Mismatch.
39 frames_read++;
40 if(newc.sync())
41 syncs_seen++;
43 //We increment the counter one time too many.
44 frames_read--;
45 //Current frame. We need to compare each control up to poll counter.
46 uint64_t readable_old_subframes = 0, readable_new_subframes = 0;
47 if(frames_read < old_movie.size())
48 readable_old_subframes = old_movie.size() - frames_read;
49 if(frames_read < new_movie.size())
50 readable_new_subframes = new_movie.size() - frames_read;
51 //Compare reset flags of current frame.
52 bool old_reset = false;
53 bool new_reset = false;
54 std::pair<short, short> old_delay = std::make_pair(0, 0);
55 std::pair<short, short> new_delay = std::make_pair(0, 0);
56 if(readable_old_subframes) {
57 old_reset = old_movie[frames_read].reset();
58 old_delay = old_movie[frames_read].delay();
60 if(readable_new_subframes) {
61 new_reset = new_movie[frames_read].reset();
62 new_delay = new_movie[frames_read].delay();
64 if(old_reset != new_reset || old_delay != new_delay)
65 return false;
66 //Then rest of the stuff.
67 for(unsigned i = 0; i < MAX_BUTTONS; i++) {
68 uint32_t p = polls[i + 4] & 0x7FFFFFFFUL;
69 short ov = 0, nv = 0;
70 for(uint32_t j = 0; j < p; j++) {
71 if(j < readable_old_subframes)
72 ov = old_movie[j + frames_read].axis2(i);
73 if(j < readable_new_subframes)
74 nv = new_movie[j + frames_read].axis2(i);
75 if(ov != nv)
76 return false;
79 return true;
83 void movie::set_all_DRDY() throw()
85 pollcounters.set_all_DRDY();
88 std::string movie::rerecord_count() throw(std::bad_alloc)
90 return rerecords;
93 void movie::rerecord_count(const std::string& count) throw(std::bad_alloc)
95 rerecords = count;
98 std::string movie::project_id() throw(std::bad_alloc)
100 return _project_id;
103 void movie::project_id(const std::string& id) throw(std::bad_alloc)
105 _project_id = id;
108 bool movie::readonly_mode() throw()
110 return readonly;
113 void movie::set_controls(controller_frame controls) throw()
115 current_controls = controls;
118 uint32_t movie::count_changes(uint64_t first_subframe) throw()
120 return movie_data.subframe_count(first_subframe);
123 controller_frame movie::get_controls() throw()
125 if(!readonly)
126 return current_controls;
127 controller_frame c = movie_data.blank_frame(false);
128 //Before the beginning? Somebody screwed up (but return released / neutral anyway)...
129 if(current_frame == 0)
130 return c;
131 //Otherwise find the last valid frame of input.
132 uint32_t changes = count_changes(current_frame_first_subframe);
133 if(!changes)
134 return c; //End of movie.
135 if(pollcounters.get_system()) {
136 c.reset(movie_data[current_frame_first_subframe].reset());
137 c.delay(movie_data[current_frame_first_subframe].delay());
139 for(size_t i = 0; i < MAX_BUTTONS; i++) {
140 uint32_t polls = pollcounters.get_polls(i);
141 uint32_t index = (changes > polls) ? polls : changes - 1;
142 c.axis2(i, movie_data[current_frame_first_subframe + index].axis2(i));
144 return c;
147 uint64_t movie::get_current_frame() throw()
149 return current_frame;
152 uint64_t movie::get_lag_frames() throw()
154 return lag_frames;
157 uint64_t movie::get_frame_count() throw()
159 return frames_in_movie;
162 void movie::next_frame() throw(std::bad_alloc)
164 //If all poll counters are zero for all real controls, this frame is lag.
165 bool this_frame_lag = !pollcounters.has_polled();
166 //Oh, frame 0 must not be considered lag.
167 if(current_frame && this_frame_lag) {
168 lag_frames++;
169 //debuglog << "Frame " << current_frame << " is lag" << std::endl << std::flush;
170 if(!readonly) {
171 //If in read-write mode, write a dummy record for the frame. Force sync flag.
172 //As index should be movie_data.size(), it is correct afterwards.
173 movie_data.append(current_controls.copy(true));
174 frames_in_movie++;
178 //Reset the poll counters and DRDY flags.
179 pollcounters.clear();
181 //Increment the current frame counter and subframe counter. Note that first subframe is undefined for
182 //frame 0 and 0 for frame 1.
183 if(current_frame)
184 current_frame_first_subframe = current_frame_first_subframe +
185 count_changes(current_frame_first_subframe);
186 else
187 current_frame_first_subframe = 0;
188 current_frame++;
191 bool movie::get_DRDY(unsigned pid, unsigned ctrl) throw(std::logic_error)
193 return pollcounters.get_DRDY(pid, ctrl);
196 short movie::next_input(unsigned pid, unsigned ctrl) throw(std::bad_alloc, std::logic_error)
198 pollcounters.clear_DRDY(pid, ctrl);
200 if(readonly) {
201 //In readonly mode...
202 //If at the end of the movie, return released / neutral (but also record the poll)...
203 if(current_frame_first_subframe >= movie_data.size()) {
204 pollcounters.increment_polls(pid, ctrl);
205 return 0;
207 //Before the beginning? Somebody screwed up (but return released / neutral anyway)...
208 if(current_frame == 0)
209 return 0;
210 //Otherwise find the last valid frame of input.
211 uint32_t changes = count_changes(current_frame_first_subframe);
212 uint32_t polls = pollcounters.increment_polls(pid, ctrl);
213 uint32_t index = (changes > polls) ? polls : changes - 1;
214 //debuglog << "Frame=" << current_frame << " Subframe=" << polls << " control=" << controlindex << " value=" << movie_data[current_frame_first_subframe + index](controlindex) << " fetchrow=" << current_frame_first_subframe + index << std::endl << std::flush;
215 return movie_data[current_frame_first_subframe + index].axis(pid, ctrl);
216 } else {
217 //Readwrite mode.
218 //Before the beginning? Somebody screwed up (but return released / neutral anyway)...
219 //Also, frame 0 must not be added to movie file.
220 if(current_frame == 0)
221 return 0;
222 //If at movie end, insert complete input with frame sync set (this is the first subframe).
223 if(current_frame_first_subframe >= movie_data.size()) {
224 movie_data.append(current_controls.copy(true));
225 //current_frame_first_subframe should be movie_data.size(), so it is right.
226 pollcounters.increment_polls(pid, ctrl);
227 frames_in_movie++;
228 //debuglog << "Frame=" << current_frame << " Subframe=" << (pollcounters[controlindex] - 1) << " control=" << controlindex << " value=" << movie_data[current_frame_first_subframe](controlindex) << " fetchrow=" << current_frame_first_subframe << std::endl << std::flush;
229 return movie_data[current_frame_first_subframe].axis(pid, ctrl);
231 short new_value = current_controls.axis(pid, ctrl);
232 //Fortunately, we know this frame is the last one in movie_data.
233 uint32_t pollcounter = pollcounters.get_polls(pid, ctrl);
234 uint64_t fetchrow = movie_data.size() - 1;
235 if(current_frame_first_subframe + pollcounter < movie_data.size()) {
236 //The index is within existing size. Change the value and propagate to all subsequent
237 //subframes.
238 for(uint64_t i = current_frame_first_subframe + pollcounter; i < movie_data.size(); i++)
239 movie_data[i].axis(pid, ctrl, new_value);
240 fetchrow = current_frame_first_subframe + pollcounter;
241 } else if(new_value != movie_data[movie_data.size() - 1].axis(pid, ctrl)) {
242 //The index is not within existing size and value does not match. We need to create a new
243 //subframes(s), copying the last subframe.
244 while(current_frame_first_subframe + pollcounter >= movie_data.size())
245 movie_data.append(movie_data[movie_data.size() - 1].copy(false));
246 fetchrow = current_frame_first_subframe + pollcounter;
247 movie_data[current_frame_first_subframe + pollcounter].axis(pid, ctrl, new_value);
249 pollcounters.increment_polls(pid, ctrl);
250 //debuglog << "Frame=" << current_frame << " Subframe=" << (pollcounters[controlindex] - 1) << " control=" << controlindex << " value=" << new_value << " fetchrow=" << fetchrow << std::endl << std::flush;
251 return new_value;
255 movie::movie() throw(std::bad_alloc)
257 readonly = false;
258 rerecords = "0";
259 _project_id = "";
260 current_frame = 0;
261 frames_in_movie = 0;
262 current_frame_first_subframe = 0;
263 lag_frames = 0;
264 clear_caches();
267 void movie::load(const std::string& rerecs, const std::string& project_id, controller_frame_vector& input)
268 throw(std::bad_alloc, std::runtime_error)
270 if(input.size() > 0 && !input[0].sync())
271 throw std::runtime_error("First subframe MUST have frame sync flag set");
272 clear_caches();
273 frames_in_movie = 0;
274 for(size_t i = 0; i < input.size(); i++)
275 if(input[i].sync())
276 frames_in_movie++;
277 readonly = true;
278 rerecords = rerecs;
279 _project_id = project_id;
280 current_frame = 0;
281 current_frame_first_subframe = 0;
282 pollcounters.clear();
283 lag_frames = 0;
284 movie_data = input;
285 //This is to force internal type of current_controls to become correct.
286 current_controls = input.blank_frame(false);
289 controller_frame_vector movie::save() throw(std::bad_alloc)
291 return movie_data;
294 void movie::commit_reset(long delay) throw(std::bad_alloc)
296 if(readonly || delay < 0)
297 return;
298 //If this frame is lagged, we need to write entry for it.
299 if(!pollcounters.has_polled()) {
300 movie_data.append(current_controls.copy(true));
301 frames_in_movie++;
302 //Current_frame_first_subframe is correct.
304 //Also set poll counters on reset cycles to avoid special cases elsewhere.
305 pollcounters.set_system();
306 //Current frame is always last in rw mode.
307 movie_data[current_frame_first_subframe].reset(true);
308 movie_data[current_frame_first_subframe].delay(std::make_pair(delay / 10000, delay % 10000));
311 unsigned movie::next_poll_number()
313 return pollcounters.max_polls() + 1;
316 void movie::readonly_mode(bool enable) throw(std::bad_alloc)
318 bool was_in_readonly = readonly;
319 readonly = enable;
320 if(was_in_readonly && !readonly) {
321 clear_caches();
322 //Transitioning to readwrite mode, we have to adjust the length of the movie data.
323 if(current_frame == 0) {
324 //WTF... At before first frame. Blank the entiere movie.
325 frames_in_movie = 0;
326 movie_data.clear();
327 return;
329 //Fun special case: Current frame is not in movie (current_frame_first_subframe >= movie_data.size()).
330 //In this case, we have to extend the movie data.
331 if(current_frame_first_subframe >= movie_data.size()) {
332 //Yes, this will insert one extra frame... But we will lose it later if it is not needed.
333 while(frames_in_movie < current_frame) {
334 movie_data.append(movie_data.blank_frame(true));
335 frames_in_movie++;
337 current_frame_first_subframe = movie_data.size() - 1;
340 //We have to take the part up to furthest currently readable subframe. Also, we need to propagate
341 //forward values with smaller poll counters.
342 uint64_t next_frame_first_subframe = current_frame_first_subframe +
343 count_changes(current_frame_first_subframe);
344 uint64_t max_readable_subframes = current_frame_first_subframe + pollcounters.max_polls();
345 if(max_readable_subframes > next_frame_first_subframe)
346 max_readable_subframes = next_frame_first_subframe;
348 movie_data.resize(max_readable_subframes);
349 next_frame_first_subframe = max_readable_subframes;
350 //Propagate RESET. This always has poll count of 0 or 1, which always behaves like 1.
351 for(uint64_t j = current_frame_first_subframe + 1; j < next_frame_first_subframe; j++) {
352 movie_data[j].reset(movie_data[current_frame_first_subframe].reset());
353 movie_data[j].delay(movie_data[current_frame_first_subframe].delay());
355 //Then the other buttons.
356 for(size_t i = 0; i < MAX_BUTTONS; i++) {
357 uint32_t polls = pollcounters.get_polls(i);
358 polls = polls ? polls : 1;
359 for(uint64_t j = current_frame_first_subframe + polls; j < next_frame_first_subframe; j++)
360 movie_data[j].axis2(i, movie_data[current_frame_first_subframe + polls - 1].axis2(i));
362 frames_in_movie = current_frame - ((current_frame_first_subframe >= movie_data.size()) ? 1 : 0);
366 //Save state of movie code.
367 void movie::save_state(std::string& proj_id, uint64_t& curframe, uint64_t& lagframes, std::vector<uint32_t>& pcounters)
368 throw(std::bad_alloc)
370 pollcounters.save_state(pcounters);
371 proj_id = _project_id;
372 curframe = current_frame;
373 lagframes = lag_frames;
376 //Restore state of movie code. Throws if state is invalid. Flag gives new state of readonly flag.
377 size_t movie::restore_state(uint64_t curframe, uint64_t lagframe, const std::vector<uint32_t>& pcounters, bool ro,
378 controller_frame_vector* old_movie, const std::string& old_projectid) throw(std::bad_alloc,
379 std::runtime_error)
381 if(!pollcounters.check(pcounters))
382 throw std::runtime_error("Wrong number of poll counters");
383 if(old_movie && !movies_compatible(*old_movie, movie_data, curframe, &pcounters[0], old_projectid,
384 _project_id))
385 throw std::runtime_error("Save is not from this movie");
386 uint64_t tmp_firstsubframe = 0;
387 for(uint64_t i = 1; i < curframe; i++)
388 tmp_firstsubframe = tmp_firstsubframe + count_changes(tmp_firstsubframe);
389 //Checks have passed, copy the data.
390 readonly = true;
391 current_frame = curframe;
392 current_frame_first_subframe = tmp_firstsubframe;
393 lag_frames = lagframe;
394 pollcounters.load_state(pcounters);
395 readonly_mode(ro);
396 return 0;
399 long movie::get_reset_status() throw()
401 if(current_frame == 0 || current_frame_first_subframe >= movie_data.size())
402 return -1; //No resets out of range.
403 if(!movie_data[current_frame_first_subframe].reset())
404 return -1; //Not a reset.
405 //Also set poll counters on reset cycles to avoid special cases elsewhere.
406 pollcounters.set_system();
407 auto g = movie_data[current_frame_first_subframe].delay();
408 long hi = g.first, lo = g.second;
409 return hi * 10000 + lo;
412 uint64_t movie::frame_subframes(uint64_t frame) throw()
414 if(frame < cached_frame)
415 clear_caches();
416 uint64_t p = cached_subframe;
417 for(uint64_t i = cached_frame; i < frame; i++)
418 p = p + count_changes(p);
419 cached_frame = frame;
420 cached_subframe = p;
421 return count_changes(p);
424 void movie::clear_caches() throw()
426 cached_frame = 1;
427 cached_subframe = 0;
430 controller_frame movie::read_subframe(uint64_t frame, uint64_t subframe) throw()
432 if(frame < cached_frame)
433 clear_caches();
434 uint64_t p = cached_subframe;
435 for(uint64_t i = cached_frame; i < frame; i++)
436 p = p + count_changes(p);
437 cached_frame = frame;
438 cached_subframe = p;
439 uint64_t max = count_changes(p);
440 if(!max) {
441 return movie_data.blank_frame(true);
443 if(max <= subframe)
444 subframe = max - 1;
445 return movie_data[p + subframe];
449 movie_logic::movie_logic() throw()
453 movie& movie_logic::get_movie() throw()
455 return mov;
458 long movie_logic::new_frame_starting(bool dont_poll) throw(std::bad_alloc, std::runtime_error)
460 mov.next_frame();
461 controller_frame c = update_controls(false);
462 if(!mov.readonly_mode()) {
463 mov.set_controls(c);
464 if(dont_poll)
465 mov.set_all_DRDY();
466 if(c.reset()) {
467 auto g = c.delay();
468 long hi = g.first, lo = g.second;
469 mov.commit_reset(hi * 10000 + lo);
472 return mov.get_reset_status();
475 short movie_logic::input_poll(bool port, unsigned dev, unsigned id) throw(std::bad_alloc, std::runtime_error)
477 unsigned pid = port ? (dev + MAX_CONTROLLERS_PER_PORT) : dev;
478 if(!mov.get_DRDY(pid, id)) {
479 mov.set_controls(update_controls(true));
480 mov.set_all_DRDY();
482 int16_t in = mov.next_input(pid, id);
483 //debuglog << "BSNES asking for (" << port << "," << dev << "," << id << ") (frame " << mov.get_current_frame()
484 // << ") giving " << in << std::endl;
485 //debuglog.flush();
486 return in;