Change the pollcounter type to avoid errors on movie load
[lsnes.git] / src / core / movie.cpp
blob76b860a866f8a9a9173f6c3a94cac9f76ad6f4bd
1 #include "lsnes.hpp"
3 #include "core/emucore.hpp"
4 #include "core/misc.hpp"
5 #include "core/movie.hpp"
6 #include "core/rom.hpp"
8 #include <stdexcept>
9 #include <cassert>
10 #include <cstring>
11 #include <fstream>
14 //std::ofstream debuglog("movie-debugging-log", std::ios::out | std::ios::app);
16 namespace
18 uint64_t find_next_sync(controller_frame_vector& movie, uint64_t after)
20 if(after >= movie.size())
21 return after;
22 do {
23 after++;
24 } while(after < movie.size() && !movie[after].sync());
25 return after;
28 bool movies_compatible(controller_frame_vector& old_movie, controller_frame_vector& new_movie,
29 uint64_t frame, const uint32_t* polls, const std::string& old_projectid,
30 const std::string& new_projectid)
32 //Project IDs have to match.
33 if(old_projectid != new_projectid)
34 return false;
35 //Types have to match.
36 if(old_movie.get_types() != new_movie.get_types())
37 return false;
38 const port_type_set& pset = new_movie.get_types();
39 //If new movie is before first frame, anything with same project_id is compatible.
40 if(frame == 0)
41 return true;
42 //Scan both movies until frame syncs are seen. Out of bounds reads behave as all neutral but frame
43 //sync done.
44 uint64_t syncs_seen = 0;
45 uint64_t frames_read = 0;
46 while(syncs_seen < frame - 1) {
47 controller_frame oldc = old_movie.blank_frame(true), newc = new_movie.blank_frame(true);
48 if(frames_read < old_movie.size())
49 oldc = old_movie[frames_read];
50 if(frames_read < new_movie.size())
51 newc = new_movie[frames_read];
52 if(oldc != newc)
53 return false; //Mismatch.
54 frames_read++;
55 if(newc.sync())
56 syncs_seen++;
58 //We increment the counter one time too many.
59 frames_read--;
60 //Current frame. We need to compare each control up to poll counter.
61 uint64_t readable_old_subframes = 0, readable_new_subframes = 0;
62 uint64_t oldlen = find_next_sync(old_movie, frames_read);
63 uint64_t newlen = find_next_sync(new_movie, frames_read);
64 if(frames_read < oldlen)
65 readable_old_subframes = oldlen - frames_read;
66 if(frames_read < newlen)
67 readable_new_subframes = newlen - frames_read;
68 //Then rest of the stuff.
69 for(unsigned i = 0; i < pset.indices(); i++) {
70 uint32_t p = polls[i] & 0x7FFFFFFFUL;
71 short ov = 0, nv = 0;
72 for(uint32_t j = 0; j < p; j++) {
73 if(j < readable_old_subframes)
74 ov = old_movie[j + frames_read].axis2(i);
75 if(j < readable_new_subframes)
76 nv = new_movie[j + frames_read].axis2(i);
77 if(ov != nv)
78 return false;
81 return true;
85 void movie::set_all_DRDY() throw()
87 pollcounters.set_all_DRDY();
90 std::string movie::rerecord_count() throw(std::bad_alloc)
92 return rerecords;
95 void movie::rerecord_count(const std::string& count) throw(std::bad_alloc)
97 rerecords = count;
100 std::string movie::project_id() throw(std::bad_alloc)
102 return _project_id;
105 void movie::project_id(const std::string& id) throw(std::bad_alloc)
107 _project_id = id;
110 bool movie::readonly_mode() throw()
112 return readonly;
115 void movie::set_controls(controller_frame controls) throw()
117 current_controls = controls;
120 uint32_t movie::count_changes(uint64_t first_subframe) throw()
122 return movie_data.subframe_count(first_subframe);
125 controller_frame movie::get_controls() throw()
127 if(!readonly)
128 return current_controls;
129 controller_frame c = movie_data.blank_frame(false);
130 //Before the beginning? Somebody screwed up (but return released / neutral anyway)...
131 if(current_frame == 0)
132 return c;
133 //Otherwise find the last valid frame of input.
134 uint32_t changes = count_changes(current_frame_first_subframe);
135 if(!changes)
136 return c; //End of movie.
137 if(pollcounters.get_polls(0, 0, 1)) {
138 //Process reset.
139 c.axis3(0, 0, 1, movie_data[current_frame_first_subframe].axis3(0, 0, 1));
140 c.axis3(0, 0, 2, movie_data[current_frame_first_subframe].axis3(0, 0, 2));
141 c.axis3(0, 0, 3, movie_data[current_frame_first_subframe].axis3(0, 0, 3));
143 for(size_t i = 0; i < movie_data.get_types().indices(); i++) {
144 uint32_t polls = pollcounters.get_polls(i);
145 uint32_t index = (changes > polls) ? polls : changes - 1;
146 c.axis2(i, movie_data[current_frame_first_subframe + index].axis2(i));
148 return c;
151 uint64_t movie::get_current_frame() throw()
153 return current_frame;
156 uint64_t movie::get_lag_frames() throw()
158 return lag_frames;
161 uint64_t movie::get_frame_count() throw()
163 return frames_in_movie;
166 void movie::next_frame() throw(std::bad_alloc)
168 //Adjust lag count. Frame 0 MUST NOT be considered lag.
169 unsigned pflag = core_get_poll_flag();
170 if(current_frame && pflag == 0)
171 lag_frames++;
172 else if(pflag < 2)
173 core_set_poll_flag(0);
175 //If all poll counters are zero for all real controls, this frame is lag.
176 bool this_frame_lag = !pollcounters.has_polled();
177 //Oh, frame 0 must not be considered lag.
178 if(current_frame && this_frame_lag) {
179 if(pflag == 2)
180 lag_frames++; //Legacy compat. behaviour.
181 //debuglog << "Frame " << current_frame << " is lag" << std::endl << std::flush;
182 if(!readonly) {
183 //If in read-write mode, write a dummy record for the frame. Force sync flag.
184 //As index should be movie_data.size(), it is correct afterwards.
185 movie_data.append(current_controls.copy(true));
186 frames_in_movie++;
190 //Reset the poll counters and DRDY flags.
191 pollcounters.clear();
193 //Increment the current frame counter and subframe counter. Note that first subframe is undefined for
194 //frame 0 and 0 for frame 1.
195 if(current_frame)
196 current_frame_first_subframe = current_frame_first_subframe +
197 count_changes(current_frame_first_subframe);
198 else
199 current_frame_first_subframe = 0;
200 current_frame++;
203 bool movie::get_DRDY(unsigned port, unsigned controller, unsigned ctrl) throw(std::logic_error)
205 return pollcounters.get_DRDY(port, controller, ctrl);
208 short movie::next_input(unsigned port, unsigned controller, unsigned ctrl) throw(std::bad_alloc, std::logic_error)
210 pollcounters.clear_DRDY(port, controller, ctrl);
212 if(readonly) {
213 //In readonly mode...
214 //If at the end of the movie, return released / neutral (but also record the poll)...
215 if(current_frame_first_subframe >= movie_data.size()) {
216 pollcounters.increment_polls(port, controller, ctrl);
217 return 0;
219 //Before the beginning? Somebody screwed up (but return released / neutral anyway)...
220 if(current_frame == 0)
221 return 0;
222 //Otherwise find the last valid frame of input.
223 uint32_t changes = count_changes(current_frame_first_subframe);
224 uint32_t polls = pollcounters.increment_polls(port, controller, ctrl);
225 uint32_t index = (changes > polls) ? polls : changes - 1;
226 //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;
227 return movie_data[current_frame_first_subframe + index].axis3(port, controller, ctrl);
228 } else {
229 //Readwrite mode.
230 //Before the beginning? Somebody screwed up (but return released / neutral anyway)...
231 //Also, frame 0 must not be added to movie file.
232 if(current_frame == 0)
233 return 0;
234 //If at movie end, insert complete input with frame sync set (this is the first subframe).
235 if(current_frame_first_subframe >= movie_data.size()) {
236 movie_data.append(current_controls.copy(true));
237 //current_frame_first_subframe should be movie_data.size(), so it is right.
238 pollcounters.increment_polls(port, controller, ctrl);
239 frames_in_movie++;
240 //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;
241 return movie_data[current_frame_first_subframe].axis3(port, controller, ctrl);
243 short new_value = current_controls.axis3(port, controller, ctrl);
244 //Fortunately, we know this frame is the last one in movie_data.
245 uint32_t pollcounter = pollcounters.get_polls(port, controller, ctrl);
246 uint64_t fetchrow = movie_data.size() - 1;
247 if(current_frame_first_subframe + pollcounter < movie_data.size()) {
248 //The index is within existing size. Change the value and propagate to all subsequent
249 //subframes.
250 for(uint64_t i = current_frame_first_subframe + pollcounter; i < movie_data.size(); i++)
251 movie_data[i].axis3(port, controller, ctrl, new_value);
252 fetchrow = current_frame_first_subframe + pollcounter;
253 } else if(new_value != movie_data[movie_data.size() - 1].axis3(port, controller, ctrl)) {
254 //The index is not within existing size and value does not match. We need to create a new
255 //subframes(s), copying the last subframe.
256 while(current_frame_first_subframe + pollcounter >= movie_data.size())
257 movie_data.append(movie_data[movie_data.size() - 1].copy(false));
258 fetchrow = current_frame_first_subframe + pollcounter;
259 movie_data[current_frame_first_subframe + pollcounter].axis3(port, controller, ctrl,
260 new_value);
262 pollcounters.increment_polls(port, controller, ctrl);
263 //debuglog << "Frame=" << current_frame << " Subframe=" << (pollcounters[controlindex] - 1) << " control=" << controlindex << " value=" << new_value << " fetchrow=" << fetchrow << std::endl << std::flush;
264 return new_value;
268 movie::movie() throw(std::bad_alloc)
270 readonly = false;
271 rerecords = "0";
272 _project_id = "";
273 current_frame = 0;
274 frames_in_movie = 0;
275 current_frame_first_subframe = 0;
276 lag_frames = 0;
277 clear_caches();
280 void movie::load(const std::string& rerecs, const std::string& project_id, controller_frame_vector& input)
281 throw(std::bad_alloc, std::runtime_error)
283 if(input.size() > 0 && !input[0].sync())
284 throw std::runtime_error("First subframe MUST have frame sync flag set");
285 clear_caches();
286 frames_in_movie = 0;
287 for(size_t i = 0; i < input.size(); i++)
288 if(input[i].sync())
289 frames_in_movie++;
290 readonly = true;
291 rerecords = rerecs;
292 _project_id = project_id;
293 current_frame = 0;
294 current_frame_first_subframe = 0;
295 pollcounters = pollcounter_vector(input.get_types());
296 lag_frames = 0;
297 movie_data = input;
298 //This is to force internal type of current_controls to become correct.
299 current_controls = input.blank_frame(false);
302 controller_frame_vector movie::save() throw(std::bad_alloc)
304 return movie_data;
307 void movie::commit_reset(long delay) throw(std::bad_alloc)
309 if(readonly || delay < 0)
310 return;
311 //If this frame is lagged, we need to write entry for it.
312 if(!pollcounters.has_polled()) {
313 movie_data.append(current_controls.copy(true));
314 frames_in_movie++;
315 //Current_frame_first_subframe is correct.
317 //Also set poll counters on reset cycles to avoid special cases elsewhere.
318 pollcounters.increment_polls(0, 0, 1);
319 //Current frame is always last in rw mode.
320 movie_data[current_frame_first_subframe].axis3(0, 0, 1, 1);
321 movie_data[current_frame_first_subframe].axis3(0, 0, 2, delay / 10000);
322 movie_data[current_frame_first_subframe].axis3(0, 0, 3, delay % 10000);
325 unsigned movie::next_poll_number()
327 return pollcounters.max_polls() + 1;
330 void movie::readonly_mode(bool enable) throw(std::bad_alloc)
332 bool was_in_readonly = readonly;
333 readonly = enable;
334 if(was_in_readonly && !readonly) {
335 clear_caches();
336 //Transitioning to readwrite mode, we have to adjust the length of the movie data.
337 if(current_frame == 0) {
338 //WTF... At before first frame. Blank the entiere movie.
339 frames_in_movie = 0;
340 movie_data.clear();
341 return;
343 //Fun special case: Current frame is not in movie (current_frame_first_subframe >= movie_data.size()).
344 //In this case, we have to extend the movie data.
345 if(current_frame_first_subframe >= movie_data.size()) {
346 //Yes, this will insert one extra frame... But we will lose it later if it is not needed.
347 while(frames_in_movie < current_frame) {
348 movie_data.append(movie_data.blank_frame(true));
349 frames_in_movie++;
351 current_frame_first_subframe = movie_data.size() - 1;
354 //We have to take the part up to furthest currently readable subframe. Also, we need to propagate
355 //forward values with smaller poll counters.
356 uint64_t next_frame_first_subframe = current_frame_first_subframe +
357 count_changes(current_frame_first_subframe);
358 uint64_t max_readable_subframes = current_frame_first_subframe + pollcounters.max_polls();
359 if(max_readable_subframes > next_frame_first_subframe)
360 max_readable_subframes = next_frame_first_subframe;
362 movie_data.resize(max_readable_subframes);
363 next_frame_first_subframe = max_readable_subframes;
364 //Propagate RESET. This always has poll count of 0 or 1, which always behaves like 1.
365 for(uint64_t j = current_frame_first_subframe + 1; j < next_frame_first_subframe; j++) {
366 movie_data[j].axis3(0, 0, 1, movie_data[current_frame_first_subframe].axis3(0, 0, 1));
367 movie_data[j].axis3(0, 0, 2, movie_data[current_frame_first_subframe].axis3(0, 0, 2));
368 movie_data[j].axis3(0, 0, 3, movie_data[current_frame_first_subframe].axis3(0, 0, 3));
370 //Then the other buttons.
371 for(size_t i = 4; i < movie_data.get_types().indices(); i++) {
372 uint32_t polls = pollcounters.get_polls(i);
373 polls = polls ? polls : 1;
374 for(uint64_t j = current_frame_first_subframe + polls; j < next_frame_first_subframe; j++)
375 movie_data[j].axis2(i, movie_data[current_frame_first_subframe + polls - 1].axis2(i));
377 frames_in_movie = current_frame - ((current_frame_first_subframe >= movie_data.size()) ? 1 : 0);
381 //Save state of movie code.
382 void movie::save_state(std::string& proj_id, uint64_t& curframe, uint64_t& lagframes, std::vector<uint32_t>& pcounters)
383 throw(std::bad_alloc)
385 pollcounters.save_state(pcounters);
386 proj_id = _project_id;
387 curframe = current_frame;
388 lagframes = lag_frames;
391 //Restore state of movie code. Throws if state is invalid. Flag gives new state of readonly flag.
392 size_t movie::restore_state(uint64_t curframe, uint64_t lagframe, const std::vector<uint32_t>& pcounters, bool ro,
393 controller_frame_vector* old_movie, const std::string& old_projectid) throw(std::bad_alloc,
394 std::runtime_error)
396 if(!pollcounters.check(pcounters))
397 throw std::runtime_error("Wrong number of poll counters");
398 if(old_movie && !movies_compatible(*old_movie, movie_data, curframe, &pcounters[0], old_projectid,
399 _project_id))
400 throw std::runtime_error("Save is not from this movie");
401 uint64_t tmp_firstsubframe = 0;
402 for(uint64_t i = 1; i < curframe; i++)
403 tmp_firstsubframe = tmp_firstsubframe + count_changes(tmp_firstsubframe);
404 //Checks have passed, copy the data.
405 readonly = true;
406 current_frame = curframe;
407 current_frame_first_subframe = tmp_firstsubframe;
408 lag_frames = lagframe;
409 pollcounters.load_state(pcounters);
410 readonly_mode(ro);
411 return 0;
414 long movie::get_reset_status() throw()
416 if(current_frame == 0 || current_frame_first_subframe >= movie_data.size())
417 return -1; //No resets out of range.
418 if(!movie_data[current_frame_first_subframe].axis3(0, 0, 1))
419 return -1; //Not a reset.
420 //Also set poll counters on reset cycles to avoid special cases elsewhere.
421 pollcounters.increment_polls(0, 0, 1);
422 long hi = movie_data[current_frame_first_subframe].axis3(0, 0, 2);
423 long lo = movie_data[current_frame_first_subframe].axis3(0, 0, 3);
424 return hi * 10000 + lo;
427 uint64_t movie::frame_subframes(uint64_t frame) throw()
429 if(frame < cached_frame)
430 clear_caches();
431 uint64_t p = cached_subframe;
432 for(uint64_t i = cached_frame; i < frame; i++)
433 p = p + count_changes(p);
434 cached_frame = frame;
435 cached_subframe = p;
436 return count_changes(p);
439 void movie::clear_caches() throw()
441 cached_frame = 1;
442 cached_subframe = 0;
445 controller_frame movie::read_subframe(uint64_t frame, uint64_t subframe) throw()
447 if(frame < cached_frame)
448 clear_caches();
449 uint64_t p = cached_subframe;
450 for(uint64_t i = cached_frame; i < frame; i++)
451 p = p + count_changes(p);
452 cached_frame = frame;
453 cached_subframe = p;
454 uint64_t max = count_changes(p);
455 if(!max) {
456 return movie_data.blank_frame(true);
458 if(max <= subframe)
459 subframe = max - 1;
460 return movie_data[p + subframe];
463 void movie::reset_state() throw()
465 readonly = true;
466 current_frame = 0;
467 current_frame_first_subframe = 0;
468 pollcounters.clear();
469 lag_frames = 0;
470 clear_caches();
473 void movie::fast_save(uint64_t& _frame, uint64_t& _ptr, uint64_t& _lagc, std::vector<uint32_t>& _counters)
475 pollcounters.save_state(_counters);
476 _frame = current_frame;
477 _ptr = current_frame_first_subframe;
478 _lagc = lag_frames;
481 void movie::fast_load(uint64_t& _frame, uint64_t& _ptr, uint64_t& _lagc, std::vector<uint32_t>& _counters)
483 readonly = true;
484 current_frame = _frame;
485 current_frame_first_subframe = (_ptr <= movie_data.size()) ? _ptr : movie_data.size();
486 lag_frames = _lagc;
487 pollcounters.load_state(_counters);
488 readonly_mode(false);
492 movie_logic::movie_logic() throw()
496 movie& movie_logic::get_movie() throw()
498 return mov;
501 long movie_logic::new_frame_starting(bool dont_poll) throw(std::bad_alloc, std::runtime_error)
503 mov.next_frame();
504 controller_frame c = update_controls(false);
505 if(!mov.readonly_mode()) {
506 mov.set_controls(c);
507 if(!dont_poll)
508 mov.set_all_DRDY();
509 if(c.axis3(0, 0, 1)) {
510 long hi = c.axis3(0, 0, 2);
511 long lo = c.axis3(0, 0, 3);
512 mov.commit_reset(hi * 10000 + lo);
514 } else if(!dont_poll)
515 mov.set_all_DRDY();
516 return mov.get_reset_status();
519 short movie_logic::input_poll(unsigned port, unsigned dev, unsigned id) throw(std::bad_alloc, std::runtime_error)
521 if(!mov.get_DRDY(port, dev, id)) {
522 mov.set_controls(update_controls(true));
523 mov.set_all_DRDY();
525 int16_t in = mov.next_input(port, dev, id);
526 //std::cerr << "BSNES asking for (" << port << "," << dev << "," << id << ") (frame "
527 // << mov.get_current_frame() << ") giving " << in << std::endl;
528 return in;