Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
SequenceClock.cpp
Go to the documentation of this file.
1#include "SequenceClock.hpp"
2
3#include <limits>
4#include <algorithm>
5
6namespace monopoly::sequence
7{
8 namespace
9 {
10 bool fitsClock(std::int64_t value) noexcept
11 {
12 return value >= std::numeric_limits<std::int32_t>::min() &&
14 }
15 }
16
17 std::string_view clockErrorName(ClockError error) noexcept
18 {
19 switch (error)
20 {
21 case ClockError::UnsupportedSequenceType: return "UnsupportedSequenceType";
22 case ClockError::UnsupportedScrollingWorld: return "UnsupportedScrollingWorld";
23 case ClockError::InvalidCadence: return "InvalidCadence";
24 case ClockError::InvalidEndingAction: return "InvalidEndingAction";
25 case ClockError::NegativeEndTime: return "NegativeEndTime";
26 case ClockError::ParentClockWentBackwards: return "ParentClockWentBackwards";
27 case ClockError::ClockOverflow: return "ClockOverflow";
28 }
29 return "UnknownClockError";
30 }
31
32 std::expected<SequenceClock, ClockError> SequenceClock::start(
33 const data::LegacySequenceRecord& record, ClockStartOptions options)
34 {
35 switch (record.chunk.id)
36 {
37 case 1: case 2: case 3: case 4: case 5:
38 case 6: case 7: case 8: case 9: case 10:
39 break;
40 default:
41 return std::unexpected(ClockError::UnsupportedSequenceType);
42 }
43
44 SequenceClock result;
45 result.timeMultiple_ = options.timeMultiple != 0 ?
46 options.timeMultiple : record.header.timeMultiple;
47 if (result.timeMultiple_ == 0)
48 return std::unexpected(ClockError::InvalidCadence);
49 result.endingAction_ = options.endingAction != 0 ?
50 options.endingAction : record.header.endingAction;
51 if (result.endingAction_ > 3)
52 return std::unexpected(ClockError::InvalidEndingAction);
53 // The decoder preserves signed disk values. Negative durations have
54 // no supported runtime contract here, especially modulo for loops.
55 if (record.header.endTime < 0)
56 return std::unexpected(ClockError::NegativeEndTime);
57 const auto authoredEndTime = record.header.endTime == 0
59 : record.header.endTime;
60 if (options.endTimeOverride && *options.endTimeOverride <= 0)
61 return std::unexpected(ClockError::NegativeEndTime);
62 result.endTime_ = options.endTimeOverride.value_or(authoredEndTime);
63 result.dropFrames_ = options.dropFrames.value_or(record.header.dropFrames);
64 std::int64_t initial = options.initialClockOffset;
65 if (result.dropFrames_ && options.parentClockAtBirth)
66 initial += static_cast<std::int64_t>(*options.parentClockAtBirth) -
68 if (!fitsClock(initial))
69 return std::unexpected(ClockError::ClockOverflow);
70 result.clock_ = static_cast<std::int32_t>(initial);
71 result.videoClock_ = record.chunk.id == 6;
72 result.soundClock_ = record.chunk.id == 5;
73 result.elapsedParentClock_ = result.clock_;
74 result.authoredEndTime_ = result.endTime_;
75 return result;
76 }
77
78 std::expected<ClockUpdate, ClockError> SequenceClock::update(
79 std::int32_t parentClock, bool forceReevaluation)
80 {
81 ClockUpdate result{ false, clock_, clock_, false, false, false, stopped_ };
82 if (stopped_) return result;
83 if (lastParentClock_ && parentClock < *lastParentClock_)
84 return std::unexpected(ClockError::ParentClockWentBackwards);
85
86 std::int64_t elapsed = lastParentClock_ ?
87 static_cast<std::int64_t>(parentClock) - *lastParentClock_ : 0;
88 if (paused_) elapsed = 0;
89 if (videoClock_)
90 {
91 const auto parentElapsed = static_cast<std::int64_t>(elapsedParentClock_) + elapsed;
92 if (!fitsClock(parentElapsed)) return std::unexpected(ClockError::ClockOverflow);
93 elapsedParentClock_ = static_cast<std::int32_t>(parentElapsed);
94 result.updated = !lastParentClock_ || forceReevaluation || pendingVideoClock_.has_value();
95 if (!lastParentClock_) result.previousClock.reset();
96 lastParentClock_ = parentClock;
97 if (pendingVideoClock_)
98 {
99 const auto supplied = *pendingVideoClock_;
100 pendingVideoClock_.reset();
101 endTime_ = std::min(authoredEndTime_, supplied.duration);
102 const bool finished = supplied.ended ||
103 (authoredEndTime_ < supplied.duration && supplied.clock >= authoredEndTime_);
104 if (finished)
105 {
106 result.hitEnd = !videoHeld_ || endingAction_ != 2;
107 result.notifyEnd = result.hitEnd;
108 clock_ = endTime_;
109 if (endingAction_ <= 1) stopped_ = true;
110 else if (endingAction_ == 2) videoHeld_ = true;
111 else
112 {
113 clock_ = 0;
114 videoHeld_ = false;
115 result.restartChildren = true;
116 }
117 }
118 else
119 {
120 // A decoded backlog may outlive the nominal duration. Only
121 // confirmed EOF (or an authored shorter clip) exposes the end.
122 const auto mediaClock = std::min(supplied.clock, endTime_ - 1);
123 if (mediaClock < clock_) result.restartChildren = true;
124 clock_ = mediaClock;
125 videoHeld_ = false;
126 }
127 }
128 result.clock = clock_;
129 result.stopped = stopped_;
130 return result;
131 }
132 // First update is immediate; skipped updates do not consume time.
133 if (lastParentClock_ && elapsed < timeMultiple_ && !forceReevaluation)
134 return result;
135 if (!dropFrames_ && elapsed > timeMultiple_) elapsed = timeMultiple_;
136 auto next = static_cast<std::int64_t>(clock_) + elapsed;
137 if (soundClock_)
138 {
139 next = clock_;
140 if (pendingSoundClock_)
141 {
142 next = *pendingSoundClock_;
143 // Retail roundoff compensation at the final 60 Hz tick.
144 if (next == endTime_ - 1) next = endTime_;
145 if (lastParentClock_ && next < clock_) result.restartChildren = true;
146 pendingSoundClock_.reset();
147 }
148 }
149 if (!fitsClock(next)) return std::unexpected(ClockError::ClockOverflow);
150
151 result.updated = true;
152 if (!lastParentClock_) result.previousClock.reset();
153 clock_ = static_cast<std::int32_t>(next);
154 lastParentClock_ = parentClock;
155 if (clock_ >= endTime_ || endingAction_ == 0)
156 {
157 result.hitEnd = true;
158 result.notifyEnd = endingAction_ != 2 ||
159 !result.previousClock || *result.previousClock < endTime_;
160 if (endingAction_ <= 1) stopped_ = true;
161 else if (endingAction_ == 2)
162 {
163 clock_ = endTime_;
164 // Monopoly C_ArtLib.h:528 enables this source optimization.
165 timeMultiple_ = 255;
166 }
167 else if (!soundClock_)
168 {
169 // Natural looping calls GoBackwardsInTime(0), NOT modulo
170 // of the overshoot. Child destruction/rebirth is separate.
171 clock_ = 0;
172 result.restartChildren = true;
173 }
174 }
175 result.clock = clock_;
176 result.stopped = stopped_;
177 return result;
178 }
179
180 std::expected<void, ClockError> SequenceClock::supplyVideoClock(
181 std::int32_t mediaClock, std::int32_t duration, bool ended)
182 {
183 if (!videoClock_) return std::unexpected(ClockError::UnsupportedSequenceType);
184 if (mediaClock < 0 || duration <= 0) return std::unexpected(ClockError::NegativeEndTime);
185 pendingVideoClock_ = VideoClockInput{mediaClock, duration, ended};
186 return {};
187 }
188 std::expected<void, ClockError> SequenceClock::supplySoundClock(std::int32_t mediaClock)
189 {
190 if (!soundClock_) return std::unexpected(ClockError::UnsupportedSequenceType);
191 if (mediaClock < 0) return std::unexpected(ClockError::NegativeEndTime);
192 pendingSoundClock_ = mediaClock;
193 return {};
194 }
195
196 std::expected<void, ClockError> SequenceClock::setPaused(
197 bool paused, std::int32_t parentClock)
198 {
199 if (stopped_) return {};
200 if (lastParentClock_ && parentClock < *lastParentClock_)
201 return std::unexpected(ClockError::ParentClockWentBackwards);
202 // SEQCMD_Pause only rebases on the paused -> unpaused transition.
203 if (paused_ && !paused) lastParentClock_ = parentClock;
204 paused_ = paused;
205 return {};
206 }
207
209 std::int32_t parentClock) noexcept
210 {
211 if (stopped_) return;
212 clock_ = -static_cast<std::int32_t>(timeMultiple_);
213 lastParentClock_ = parentClock;
214 if (videoClock_)
215 {
216 elapsedParentClock_ = clock_;
217 pendingVideoClock_.reset();
218 videoHeld_ = false;
219 }
220 }
221
223 {
224 ClockUpdate result{ !stopped_, clock_, clock_, false, false, false, stopped_ };
225 if (stopped_) return result;
226 if (newTime < 0 || (newTime >= endTime_ && endingAction_ == 1))
227 {
228 stopped_ = true;
229 result.stopped = true;
230 return result;
231 }
232 lastParentClock_ = parentClock;
233 if (endingAction_ == 3) newTime %= endTime_;
234 else if (endingAction_ == 2 && newTime > endTime_) newTime = endTime_;
235 clock_ = newTime;
236 pendingSoundClock_.reset();
237 if (videoClock_)
238 {
239 elapsedParentClock_ = newTime;
240 pendingVideoClock_.reset();
241 videoHeld_ = false;
242 }
243 result.clock = clock_;
244 result.restartChildren = true;
245 return result;
246 }
247
248 std::expected<void, ClockError> SequenceClock::setEndingAction(std::uint8_t action)
249 {
250 // Public LE_SEQNCR_SetEndingAction accepts 1..3, not suicide zero.
251 if (action == 0 || action > 3)
252 return std::unexpected(ClockError::InvalidEndingAction);
253 endingAction_ = action;
254 // In particular, retain cadence 255 after a held end.
255 return {};
256 }
257}
unsigned index
std::atomic< bool > finished
std::string error
std::uint16_t value
Definition Timers.cpp:16
std::expected< void, ClockError > supplySoundClock(std::int32_t mediaClock)
ClockUpdate seek(std::int32_t newTime, std::int32_t parentClock)
void hibernateScrollingWorld(std::int32_t parentClock) noexcept
std::expected< ClockUpdate, ClockError > update(std::int32_t parentClock, bool forceReevaluation=false)
static std::expected< SequenceClock, ClockError > start(const data::LegacySequenceRecord &record, ClockStartOptions options={})
std::expected< void, ClockError > setPaused(bool paused, std::int32_t parentClock)
std::expected< void, ClockError > supplyVideoClock(std::int32_t mediaClock, std::int32_t duration, bool ended)
std::expected< void, ClockError > setEndingAction(std::uint8_t action)
static constexpr std::int32_t InfiniteEndTime
std::string_view clockErrorName(ClockError error) noexcept
std::optional< std::int32_t > endTimeOverride
std::optional< std::int32_t > parentClockAtBirth