18 std::size_t
offset, std::string detail)
19 {
return {code,
id,
offset, std::move(detail), {}}; }
25 auto detail =
"sequence dependency failed (DataID " +
26 std::to_string(
id) +
", offset " + std::to_string(
offset) +
")";
27 if constexpr (
requires { cause.detail; })
28 detail +=
": " + cause.detail;
29 else if constexpr (
requires {
static_cast<int>(cause); })
30 detail +=
": error " + std::to_string(
static_cast<int>(cause));
31 auto result =
error(code,
id,
offset, std::move(detail));
32 result.cause = std::move(cause);
40 if (dimensionality == 2)
41 return multiply(std::get<Matrix2D>(
tweeker), std::get<Matrix2D>(local));
42 if (dimensionality == 3)
43 return multiply(std::get<Matrix3D>(
tweeker), std::get<Matrix3D>(local));
47 const data::LegacySequenceAttributes& attributes,
48 std::uint8_t dimensionality)
noexcept
50 SequenceMeshChoice3D result{};
51 if (dimensionality != 3)
return result;
52 for (
const auto&
attribute : attributes.values)
55 result = {choice->meshIndexA, choice->meshIndexB,
56 choice->meshProportion};
61 const data::LegacySequenceAttributes& attributes,
62 std::uint8_t dimensionality)
noexcept
64 if (dimensionality != 3)
return std::nullopt;
65 std::optional<SequenceBounds3D> result;
66 for (
const auto&
attribute : attributes.values)
69 std::get_if<data::Sequence3DBoundingBoxAttribute>(&
attribute))
71 SequenceBounds3D bounds{
box->points.front(),
box->points.front()};
72 for (
const auto& point :
box->points)
75 bounds.minimum[
axis] =
76 std::min(bounds.minimum[
axis], point[
axis]);
77 bounds.maximum[
axis] =
78 std::max(bounds.maximum[
axis], point[
axis]);
82 else if (
const auto*
sphere =
83 std::get_if<data::Sequence3DBoundingSphereAttribute>(&
attribute))
85 const float radius = std::abs(
sphere->radius);
86 result = SequenceBounds3D{
87 {-radius, -radius, -radius},
88 {radius, radius, radius}};
94 struct SequenceAudioState
102 const data::LegacySequenceRecord& record,
103 const data::LegacySequenceAttributes& attributes)
noexcept
105 SequenceAudioState result;
106 if (!std::holds_alternative<data::SequenceSoundData>(record.data) &&
107 !std::holds_alternative<data::SequenceVideoData>(record.data))
115 for (
const auto&
attribute : attributes.values)
117 if (
const auto*
pitch =
118 std::get_if<data::SequenceSoundPitchAttribute>(&
attribute))
119 result.pitch =
pitch->pitch;
120 else if (
const auto*
volume =
121 std::get_if<data::SequenceSoundVolumeAttribute>(&
attribute))
122 result.volume = std::min<std::uint8_t>(
volume->volume, 100U);
123 else if (
const auto* pan =
124 std::get_if<data::SequenceSoundPanningAttribute>(&
attribute))
125 result.panning = std::clamp<std::int8_t>(
126 pan->panning, -100, 100);
132 const data::LegacySequenceRecord& record,
133 const data::LegacySequenceAttributes& attributes)
noexcept
135 std::uint8_t result{};
136 for (
const auto&
attribute : attributes.values)
139 result =
label->labelNumber;
143 if (
const auto* camera =
144 std::get_if<data::SequenceCameraData>(&record.data))
145 result = camera->cameraLabel;
150 const data::LegacySequenceAttributes& attributes)
153 for (
const auto&
attribute : attributes.values)
156 result = file->fileName;
161 const data::ResourceSnapshot& resources,
162 std::string_view fileName,
168 return std::unexpected(
caused(
172 std::ifstream
input(*
path, std::ios::binary | std::ios::ate);
174 return std::unexpected(
error(
177 "cannot open preloader external file"));
179 const auto end =
input.tellg();
181 return std::unexpected(
error(
184 "cannot size preloader external file"));
186 auto bytes = std::make_shared<data::DataBytes>(
187 static_cast<std::size_t
>(end));
188 input.seekg(0, std::ios::beg);
189 if (!
bytes->empty() &&
191 reinterpret_cast<char*
>(
bytes->data()),
192 static_cast<std::streamsize
>(
bytes->size())))
193 return std::unexpected(
error(
196 "cannot read preloader external file"));
198 return std::shared_ptr<const data::DataBytes>(
202 std::optional<data::Sequence2DBoundingBoxAttribute>
boundingBox2D(
203 const data::LegacySequenceAttributes& attributes)
205 for (
const auto&
attribute : attributes.values)
214 const auto rounded = std::nearbyint(
static_cast<double>(
value));
215 return static_cast<std::int32_t
>(std::clamp(
217 static_cast<double>(std::numeric_limits<std::int32_t>::min()),
218 static_cast<double>(std::numeric_limits<std::int32_t>::max())));
222 const Matrix2D&
matrix, std::int32_t
x, std::int32_t
y)
noexcept
225 const float newX =
m[0] *
static_cast<float>(
x) +
226 m[3] *
static_cast<float>(
y) +
m[6];
227 const float newW =
m[2] *
static_cast<float>(
x) +
228 m[5] *
static_cast<float>(
y) +
m[8];
233 const data::LegacySequenceAttributes& attributes,
234 const Matrix2D& worldTransform)
noexcept
237 const std::int32_t
left =
box ?
box->left : 0;
239 const std::int32_t
top =
box ?
box->top : 0;
241 const std::int64_t
sum =
246 return static_cast<std::int32_t
>(
sum / 4);
250 const data::LegacySequenceAttributes& attributes,
251 std::uint8_t dimensionality)
noexcept
253 if (!std::holds_alternative<data::SequenceCameraData>(record.data))
return 0.0F;
254 float result = dimensionality == 3 ? 0.7853981633974F : 1.0F;
255 for (
const auto&
attribute : attributes.values)
258 result =
field->fieldOfView;
264 const data::LegacySequenceAttributes& attributes,
265 std::uint8_t interpolationType, std::int32_t clock,
268 if (interpolationType > 2)
270 if (interpolationType == 0)
return std::nullopt;
272 const auto*
first = std::get_if<data::SequenceCameraFieldOfViewAttribute>(
keys.first);
273 const auto* second = std::get_if<data::SequenceCameraFieldOfViewAttribute>(
keys.second);
274 if (!
first)
return std::nullopt;
277 float result =
first->fieldOfView;
278 if (interpolationType == 2 && second && endTime < 1'234'567'890)
280 const float proportion =
static_cast<float>(clock) /
281 static_cast<float>(endTime);
282 result +=
proportion * (second->fieldOfView - result);
287 struct TweekerAudioUpdate
289 std::optional<std::uint16_t>
pitch;
290 std::optional<std::uint8_t>
volume;
291 std::optional<std::int8_t>
panning;
299 return static_cast<int>(
304 std::expected<TweekerAudioUpdate, TweekerTransformError>
306 const data::LegacySequenceAttributes& attributes,
307 std::uint8_t interpolationType, std::int32_t clock,
310 TweekerAudioUpdate result;
313 if (interpolationType == 0)
316 const auto*
pitchA = std::get_if<data::SequenceSoundPitchAttribute>(
keys.first);
317 const auto*
pitchB = std::get_if<data::SequenceSoundPitchAttribute>(
keys.second);
318 const auto*
volumeA = std::get_if<data::SequenceSoundVolumeAttribute>(
keys.first);
319 const auto*
volumeB = std::get_if<data::SequenceSoundVolumeAttribute>(
keys.second);
320 const auto*
panA = std::get_if<data::SequenceSoundPanningAttribute>(
keys.first);
321 const auto*
panB = std::get_if<data::SequenceSoundPanningAttribute>(
keys.second);
326 return std::unexpected(
329 const bool linear = interpolationType == 2 &&
330 endTime < 1'234'567'890;
332 ?
static_cast<float>(clock) /
static_cast<float>(endTime)
341 result.pitch =
static_cast<std::uint16_t
>(
342 std::clamp(
value, 0, 65'535));
350 result.volume =
static_cast<std::uint8_t
>(
351 std::clamp(
value, 0, 100));
359 result.panning =
static_cast<std::int8_t
>(
360 std::clamp(
value, -100, 100));
367 std::uint8_t interpolationType, std::int32_t clock,
370 if (interpolationType == 0)
return std::nullopt;
372 const auto*
first = std::get_if<data::Sequence3DMeshChoiceAttribute>(
keys.first);
373 const auto* second = std::get_if<data::Sequence3DMeshChoiceAttribute>(
keys.second);
374 if (!
first)
return std::nullopt;
377 SequenceMeshChoice3D result{
379 if (interpolationType == 2 && second && endTime < 1'234'567'890)
381 const float proportion =
static_cast<float>(clock) /
382 static_cast<float>(endTime);
386 result = {second->meshIndexA, second->meshIndexB,
387 second->meshProportion};
390 (second->meshProportion -
first->meshProportion);
396 std::expected<std::shared_ptr<const SequenceProgram>, RuntimeError>
403 "description depth must be 1..128 and node budget nonzero"));
408 "raw bitmap sequence supports only DataBMP, DataUAP and DataNative"));
419 return std::unexpected(std::visit([&](
const auto& cause) {
421 }, children.error()));
422 program->descriptions_.push_back({
id, std::move(record),
423 std::move(*children), {},
id, {}});
424 program->descriptions_.back().zeroBitmapOffset =
true;
425 return std::shared_ptr<const SequenceProgram>(std::move(program));
428 std::expected<std::shared_ptr<const SequenceProgram>,
RuntimeError>
434 fileName.find(
'\0') != std::string::npos ||
437 "runtime video requires an ID, filename and positive destination rectangle"));
450 "cannot construct empty runtime video child schedule"));
453 attributes.
values.push_back(bounds);
454 attributes.
values.push_back(
456 program->descriptions_.push_back({
id, std::move(record),
457 std::move(*children), std::move(attributes), std::nullopt, {}, binkDoubleSize});
458 return std::shared_ptr<const SequenceProgram>(std::move(program));
461 std::expected<std::shared_ptr<const SequenceProgram>,
RuntimeError>
468 "description depth must be 1..128 and node budget nonzero"));
488 id,
offset,
"raw bitmap sequence must start at offset zero"));
496 id,
offset,
"raw HMD sequence must start at offset zero"));
507 return std::unexpected(std::visit([&](
const auto& cause) {
509 }, children.error()));
510 program->descriptions_.push_back({
id, std::move(record),
511 std::move(*children), {},
id, {}});
512 return std::shared_ptr<const SequenceProgram>(std::move(program));
516 struct Entry { std::size_t
index;
bool active; std::size_t
height; };
517 std::map<std::pair<data::DataId, std::size_t>, Entry>
visited;
519 std::function<std::expected<std::size_t, RuntimeError>(
data::DataId, std::size_t, std::size_t)>
visit;
521 std::size_t
depth) -> std::expected<std::size_t, RuntimeError>
525 currentOffset,
"description path exceeds configured depth"));
529 if (
found->second.active)
531 currentId, currentOffset,
"recursive sequence description reference"));
534 currentOffset,
"shared description exceeds depth on this path"));
535 return found->second.index;
539 currentId, currentOffset,
"description count budget exceeded"));
545 if (currentOffset != 0)
547 currentId, currentOffset,
"raw Wave sequence must start at offset zero"));
552 if (!children)
return std::unexpected(std::visit([&](
const auto& cause) {
554 }, children.error()));
555 const auto index = program->descriptions_.size();
556 program->descriptions_.push_back({
currentId,
559 std::move(*children), {},
currentId, {}});
565 currentId, currentOffset, reader.error()));
566 const auto positioned = reader->seek(currentOffset);
568 currentId, currentOffset, positioned.error()));
571 currentId, currentOffset, record.error()));
572 if (record->chunk.id != 1 && record->chunk.id != 2 &&
573 record->chunk.id != 3 && record->chunk.id != 4 &&
574 record->chunk.id != 5 && record->chunk.id != 6 &&
575 record->chunk.id != 7 && record->chunk.id != 8 &&
576 record->chunk.id != 9 && record->chunk.id != 10)
579 "runtime currently executes grouping, indirect, 2D bitmap, 3D model, sound, video intent, camera, preloader, 3D mesh and tweeker records only"));
582 currentId, currentOffset, attributes.error()));
583 for (
const auto&
attribute : attributes->values)
585 std::get_if<data::SequenceUnsupportedAttribute>(&
attribute))
588 "sequence attribute is decoded but its effect is not executed"));
592 if (
const auto*
indirect = std::get_if<data::SequenceIndirectData>(&record->data))
603 auto schedule = [&]() -> std::expected<SequenceChildSchedule, ChildScheduleError> {
607 auto bytes = std::make_shared<data::DataBytes>();
615 return std::unexpected(std::visit([&](
const auto& cause) {
617 }, schedule.error()));
620 currentId, currentOffset,
"description reference budget exceeded"));
622 std::optional<data::DataId> contentsDataId;
623 if (
const auto*
bitmap = std::get_if<data::SequenceBitmapData>(&record->data))
626 else if (
const auto* model = std::get_if<data::SequenceModelData>(&record->data))
629 else if (
const auto* sound =
630 std::get_if<data::SequenceSoundData>(&record->data))
636 record->header, sound->soundDataId,
currentId);
639 std::get_if<data::SequencePreloaderData>(&record->data))
645 else if (
const auto* mesh = std::get_if<data::SequenceMeshData>(&record->data))
648 const auto index = program->descriptions_.size();
649 program->descriptions_.push_back({
currentId, *record, *schedule,
650 std::move(*attributes), contentsDataId, {}});
653 for (
const auto&
child : schedule->records())
657 program->descriptions_[
index].childDescriptions.push_back(*
target);
659 {schedule->containingDataId(),
child.chunk.headerOffset}).
height;
666 if (!
root)
return std::unexpected(
root.error());
667 return std::shared_ptr<const SequenceProgram>(std::move(program));
670 std::expected<std::shared_ptr<const SequenceProgram>,
RuntimeError>
675 id,
offset,
"resource snapshot is null"));
677 if (!result)
return result;
679 auto program = std::const_pointer_cast<SequenceProgram>(*result);
686 -> std::expected<void, RuntimeError>
696 failure.detail =
"sequence preload failed: " +
bytes.error().detail;
697 return std::unexpected(std::move(
failure));
713 return std::unexpected(
loaded.error());
715 else if (program->descriptions_.front().record.chunk.id == 1)
717 for (
const auto childIndex : program->descriptions_.front().childDescriptions)
720 if ((std::holds_alternative<data::SequenceBitmapData>(
child.record.data) ||
721 std::holds_alternative<data::SequenceSoundData>(
child.record.data)) &&
722 child.contentsDataId)
726 return std::unexpected(
loaded.error());
730 return std::unexpected(
loaded.error());
732 program->resources_ = std::move(
resources);
736 {
return descriptions_; }
738 {
return resources_; }
744 std::shared_ptr<const SequenceProgram>
program;
790 const auto&
def = node.definition();
791 events_.push_back({
kind, node.id, node.parent ? node.parent->id : 0,
792 def.dataId,
def.record.chunk.headerOffset, node.priority,
793 node.labelNumber,
def.record.chunk.id, node.soundFailed ? std::uint8_t{0} : node.clock.endingAction(),
794 node.clock.clock()});
796 void SequenceRuntime::insert(Nodes&
siblings, std::unique_ptr<Node> node)
799 const auto position = std::lower_bound(
siblings.begin(),
siblings.end(), node->priority,
800 [](
const auto&
child, std::uint16_t
priority) { return child->priority < priority; });
801 siblings.insert(position, std::move(node));
803 std::expected<std::unique_ptr<SequenceRuntime::Node>, RuntimeError>
804 SequenceRuntime::create(std::shared_ptr<const SequenceProgram> program,
805 std::size_t description, Node* parent, std::uint16_t
priority,
806 ClockStartOptions options, std::uint8_t labelOverride)
808 const auto&
def = program->descriptions()[description];
809 const auto id =
def.dataId;
810 const auto offset =
def.record.chunk.headerOffset;
815 if (nextId_ == std::numeric_limits<SequenceNodeId>::max())
817 if (parent) options.parentClockAtBirth = parent->clock.clock();
818 else options.parentClockAtBirth.reset();
820 if (std::holds_alternative<data::SequenceSoundData>(
822 !options.endTimeOverride)
824 if (
const auto resources = program->resources())
827 if (
def.contentsDataId)
832 return std::unexpected(
caused(
841 const auto fileName =
843 if (!fileName.empty())
846 *resources, fileName,
id,
offset);
848 return std::unexpected(
loaded.error());
855 const std::span<const std::uint8_t>
riff(
856 reinterpret_cast<const std::uint8_t*
>(
859 const auto duration =
862 duration <=
static_cast<std::uint32_t
>(
863 std::numeric_limits<std::int32_t>::max()))
864 options.endTimeOverride =
865 static_cast<std::int32_t
>(duration);
873 parent ? parent->dimensionality : 0);
879 parent ? parent->dimensionality : 0);
882 if (std::holds_alternative<data::SequencePreloaderData>(
def.record.data))
884 if (
const auto resources = program->resources())
886 if (
def.contentsDataId)
890 return std::unexpected(
caused(
898 if (!fileName.empty())
901 *resources, fileName,
id,
offset);
903 return std::unexpected(
loaded.error());
910 auto node = std::make_unique<Node>(Node{nextId_++, parent, std::move(program),
911 description,
priority, *clock,
def.children, {},
true,
true,
false,
919 node->pitch = audio.pitch;
920 node->volume = audio.volume;
921 node->panning = audio.panning;
922 node->preloadedData = std::move(preloadedData);
923 if (labelOverride != 0)
924 node->labelNumber = labelOverride;
925 if (node->labelNumber != 0)
926 labelOwners_[node->labelNumber] = node->id;
927 ++liveNodes_; ++births_;
932 std::shared_ptr<const SequenceProgram> program, std::uint16_t
priority,
934 std::uint8_t labelOverride)
936 events_.clear(); births_ = 0;
937 if (!program || program->descriptions().empty())
940 std::move(program), 0,
nullptr,
priority, options, labelOverride);
941 if (!node)
return std::unexpected(node.error());
942 if (initialTransform) move(**node, *initialTransform);
943 const auto id = (*node)->id;
944 insert(roots_, std::move(*node));
947 void SequenceRuntime::destroyChildren(Node& node)
949 for (
auto&
child : node.children) destroy(
child);
950 node.children.clear();
952 void SequenceRuntime::destroy(std::unique_ptr<Node>& node)
954 destroyChildren(*node);
955 if (node->labelNumber != 0 &&
956 labelOwners_[node->labelNumber] == node->id)
957 labelOwners_[node->labelNumber] = 0;
962 void SequenceRuntime::clearForest()
964 for (
auto& node : roots_) destroy(node);
968 void SequenceRuntime::erase(Node& node)
970 auto&
siblings = node.parent ? node.parent->children : roots_;
972 [&](
const auto& current) { return current.get() == &node; });
976 SequenceRuntime::Node* SequenceRuntime::find(
SequenceNodeId id)
const
978 const auto visit = [&](
const auto&
self,
const Nodes&
nodes) -> Node* {
979 for (
const auto& node :
nodes)
981 if (node->id ==
id)
return node.get();
988 void SequenceRuntime::forceAncestors(Node& node)
990 void SequenceRuntime::forceDescendants(Node& node)
992 node.reevaluate =
true;
993 node.redrawRequested =
true;
994 for (
auto&
child : node.children) forceDescendants(*
child);
996 void SequenceRuntime::clearRedrawFlags()
999 for (
auto& node :
nodes)
1001 node->needsRedraw =
false;
1009 bool changed =
true;
1010 if (node.dimensionality == 2)
1012 if (
const auto*
matrix = std::get_if<Matrix2D>(&transform))
1014 const auto&
current = std::get<Matrix2D>(node.localTransform);
1015 changed = std::memcmp(
current.values.data(),
matrix->values.data(),
1019 node.localTransform = *
matrix;
1020 node.explicitlyPositioned =
true;
1026 node.explicitlyPositioned =
false;
1029 else if (node.dimensionality == 3)
1031 if (
const auto*
matrix = std::get_if<Matrix3D>(&transform))
1033 const auto&
current = std::get<Matrix3D>(node.localTransform);
1034 changed = std::memcmp(
current.values.data(),
matrix->values.data(),
1038 node.localTransform = *
matrix;
1039 node.explicitlyPositioned =
true;
1045 node.explicitlyPositioned =
false;
1048 if (!changed)
return;
1053 node.positionRecalc =
true;
1054 forceAncestors(node);
1055 forceDescendants(node);
1060 auto* node = find(
id);
1067 auto* node = find(
id);
1068 if (!node || node->definition().record.chunk.id != 5)
1070 "failed sound references an inactive sound sequence"));
1073 node->soundFailed =
true;
1074 forceAncestors(*node);
1080 auto* node = find(
id);
1082 "sound clock references an inactive sequence"));
1085 node->definition().dataId, node->definition().record.chunk.headerOffset,
supplied.error()));
1087 for (
auto* parent = node->parent; parent; parent = parent->parent)
1088 parent->reevaluate =
true;
1094 auto* node = find(
id);
1096 "video clock references an inactive sequence"));
1099 node->definition().dataId, node->definition().record.chunk.headerOffset,
supplied.error()));
1102 forceAncestors(*node);
1108 auto* node = find(
id);
1110 auto result = node->clock.setPaused(paused, node->parent ? node->parent->clock.clock() : parentClock_);
1112 node->definition().dataId, node->definition().record.chunk.headerOffset, result.error()));
1118 auto* node = find(
id);
1120 const auto result = node->clock.setEndingAction(action);
1122 node->definition().dataId, node->definition().record.chunk.headerOffset, result.error()));
1123 forceAncestors(*node);
1131 auto* node = find(
id);
1133 return std::unexpected(
error(
1135 "node no longer exists"));
1137 const auto&
data = node->definition().record.data;
1138 if (std::holds_alternative<data::SequenceSoundData>(
data) ||
1139 std::holds_alternative<data::SequenceVideoData>(
data))
1140 node->commandedVolume = std::min<std::uint8_t>(
volume, 100U);
1148 auto* node = find(
id);
1150 return std::unexpected(
error(
1152 "node no longer exists"));
1153 const auto&
data = node->definition().record.data;
1154 if (std::holds_alternative<data::SequenceSoundData>(
data) ||
1155 std::holds_alternative<data::SequenceVideoData>(
data))
1156 node->commandedPitch =
pitch;
1164 auto* node = find(
id);
1166 return std::unexpected(
error(
1168 "node no longer exists"));
1169 const auto&
data = node->definition().record.data;
1170 if (std::holds_alternative<data::SequenceSoundData>(
data) ||
1171 std::holds_alternative<data::SequenceVideoData>(
data))
1172 node->commandedPanning =
static_cast<std::int8_t
>(
1173 std::clamp<int>(
panning, -100, 100));
1177 std::expected<void, RuntimeError>
1181 auto* node = find(
id);
1183 return std::unexpected(
error(
1185 "scrolling-world visibility references an inactive sequence"));
1186 if (!node->definition().record.header.scrollingWorld)
1188 if (node->scrollingOnScreen == onScreen)
1191 node->scrollingOnScreen = onScreen;
1192 node->scrollingHibernating =
false;
1193 node->reevaluate =
true;
1194 forceAncestors(*node);
1198 std::expected<void, RuntimeError> SequenceRuntime::birthChildren(Node& node,
1199 std::optional<std::int32_t>
previous)
1201 const auto selected = node.schedule.select(
previous, node.clock.clock());
1203 node.definition().dataId, node.definition().record.chunk.headerOffset, selected.error()));
1204 for (
const auto index : *selected)
1206 const auto description = node.definition().childDescriptions[
index];
1207 const auto priority = node.program->descriptions()[description].record.header.priority;
1208 auto child = create(node.program, description, &node,
priority, {});
1209 if (!
child)
return std::unexpected(
child.error());
1210 insert(node.children, std::move(*
child));
1214 std::expected<void, RuntimeError> SequenceRuntime::rebuildChildren(Node& node)
1216 destroyChildren(node);
1217 node.schedule.rewind();
1218 auto born = birthChildren(node, std::nullopt);
1220 for (
auto iterator = node.children.begin();
iterator != node.children.end();)
1223 const auto time =
static_cast<std::int64_t
>(node.clock.clock()) -
1224 child.definition().record.header.parentStartTime;
1225 if (
time < std::numeric_limits<std::int32_t>::min() ||
time > std::numeric_limits<std::int32_t>::max())
1228 const auto alive = seekNode(
child,
static_cast<std::int32_t
>(
time));
1229 if (!
alive)
return std::unexpected(
alive.error());
1235 std::expected<bool, RuntimeError> SequenceRuntime::seekNode(Node& node, std::int32_t
time)
1237 const auto result = node.clock.seek(
time, node.parent ? node.parent->clock.clock() : parentClock_);
1238 if (result.stopped)
return false;
1239 node.reevaluate =
true;
1240 ++node.seekGeneration;
1242 const auto rebuilt = rebuildChildren(node);
1248 events_.clear(); births_ = 0;
1249 auto* node = find(
id);
1251 forceAncestors(*node);
1252 const auto alive = seekNode(*node,
time);
1253 if (!
alive) { clearForest();
return std::unexpected(
alive.error()); }
1254 if (!*
alive) erase(*node);
1257 void SequenceRuntime::updatePosition(Node& node)
1260 node.tweekerTransformApplied, node.localTransform, node.dimensionality);
1262 node.dimensionality,
1264 node.parent ? node.parent->dimensionality : 0);
1265 if (node.positionRecalc)
1267 ++node.movementRevision;
1268 node.needsRedraw =
true;
1269 if (node.dimensionality == 2 &&
1270 std::holds_alternative<data::SequenceSoundData>(node.definition().record.data))
1275 node.commandedVolume.reset();
1276 node.commandedPanning.reset();
1281 std::expected<bool, RuntimeError> SequenceRuntime::updateNode(Node& node, std::int32_t
parentClock)
1283 if (node.soundFailed)
1288 node.needsRedraw = node.redrawRequested;
1289 node.redrawRequested =
false;
1291 if (node.definition().record.header.scrollingWorld &&
1292 !node.scrollingOnScreen)
1294 if (!node.scrollingHibernating)
1296 destroyChildren(node);
1297 node.schedule.rewind();
1298 node.scrollingHibernating =
true;
1301 node.needsRedraw =
false;
1306 if (node.positionRecalc)
1307 updatePosition(node);
1308 node.positionRecalc =
false;
1309 node.reevaluate =
false;
1313 const auto tick = node.clock.update(
parentClock, node.reevaluate);
1315 node.definition().dataId, node.definition().record.chunk.headerOffset, tick.error()));
1316 if (!tick->updated)
return true;
1319 if (tick->stopped)
return false;
1320 const auto children = tick->restartChildren ? rebuildChildren(node) : birthChildren(node, tick->previousClock);
1321 if (!children)
return std::unexpected(children.error());
1322 if (node.definition().record.chunk.id == 10)
1324 const auto applied = applyTweeker(node);
1329 for (
auto iterator = node.children.begin();
iterator != node.children.end();)
1331 if ((*iterator)->definition().record.chunk.id != 10) { ++
iterator;
continue; }
1332 const auto alive = updateNode(**
iterator, node.clock.clock());
1333 if (!
alive)
return std::unexpected(
alive.error());
1337 updatePosition(node);
1338 for (
auto iterator = node.children.begin();
iterator != node.children.end();)
1340 if ((*iterator)->definition().record.chunk.id == 10) { ++
iterator;
continue; }
1341 if (node.positionRecalc)
1343 (*iterator)->positionRecalc =
true;
1344 (*iterator)->reevaluate =
true;
1346 const auto alive = updateNode(**
iterator, node.clock.clock());
1347 if (!
alive)
return std::unexpected(
alive.error());
1351 if ((*iterator)->needsRedraw) node.needsRedraw =
true;
1355 node.positionRecalc =
false;
1356 node.reevaluate =
false;
1359 std::expected<void, RuntimeError> SequenceRuntime::applyTweeker(Node& node)
1362 node.definition().dataId, node.definition().record.chunk.headerOffset,
1363 "a tweeker must be a child of the sequence it modifies"));
1364 const auto&
tweeker = std::get<data::SequenceTweekerData>(node.definition().record.data);
1366 if (
tweeker.interpolationType != 0)
1370 node.clock.endTime() < 1'234'567'890
1371 ?
static_cast<float>(node.clock.clock()) /
static_cast<float>(node.clock.endTime())
1375 if (node.tweekerProportion ==
proportion)
return {};
1378 tweeker.interpolationType, node.clock.clock(), node.clock.endTime(),
1379 node.parent->dimensionality);
1382 node.definition().dataId, node.definition().record.chunk.headerOffset,
1384 "tweeker interpolation type is not implemented" :
1385 "tweeker transform dimensionality does not match its parent"));
1388 tweeker.interpolationType, node.clock.clock(), node.clock.endTime(),
1389 node.parent->dimensionality);
1392 node.definition().dataId, node.definition().record.chunk.headerOffset,
1393 "3D mesh-choice tweeker dimensionality does not match its parent"));
1396 node.parent->tweekerTransformApplied = !
evaluated->identity;
1397 node.parent->tweekerTransform =
evaluated->transform;
1398 node.parent->positionRecalc =
true;
1402 node.parent->meshChoice = **meshChoice;
1405 node.parent->positionRecalc =
true;
1408 node.definition().attributes,
tweeker.interpolationType,
1409 node.clock.clock(), node.clock.endTime(),
1410 node.parent->definition().record.chunk.id == 7);
1413 node.definition().dataId, node.definition().record.chunk.headerOffset,
1414 "camera field-of-view tweeker must target a camera sequence"));
1417 const auto&
parentData = node.parent->definition().record.data;
1419 std::holds_alternative<data::SequenceSoundData>(
parentData) ||
1420 std::holds_alternative<data::SequenceVideoData>(
parentData);
1422 node.definition().attributes,
tweeker.interpolationType,
1426 node.definition().dataId,
1427 node.definition().record.chunk.headerOffset,
1428 "sound tweeker must target a sound or video sequence"));
1431 node.parent->pitch = *audio->pitch;
1432 node.parent->commandedPitch.reset();
1434 const bool directAudio = node.parent->dimensionality == 0 ||
1435 std::holds_alternative<data::SequenceVideoData>(
parentData);
1438 node.parent->volume = *audio->volume;
1440 node.parent->commandedVolume.reset();
1442 node.parent->positionRecalc =
true;
1446 node.parent->panning = *audio->panning;
1450 node.parent->commandedPanning.reset();
1457 events_.clear(); births_ = 0;
1465 if (!
alive) { clearForest();
return std::unexpected(
alive.error()); }
1472 std::uint16_t
priority,
bool wholeTree)
const
1474 std::vector<SequenceNodeId>
matches;
1475 const auto visit = [&](
const auto&
self,
const Nodes&
nodes) ->
void {
1476 for (
const auto& node :
nodes)
1478 if (node->definition().dataId ==
id && node->priority ==
priority &&
1479 node->definition().record.chunk.headerOffset == 0)
matches.push_back(node->id);
1480 if (wholeTree)
self(
self, node->children);
1487 std::uint16_t
priority,
bool wholeTree)
1491 std::size_t stopped{};
1496 if (
auto* node = find(
match))
1508 if (action == 0 || action > 3)
1513 auto* node = find(
match);
1514 (
void)node->clock.setEndingAction(action);
1515 forceAncestors(*node);
1522 std::uint8_t
volume,
bool wholeTree)
1529 auto* node = find(
match);
1530 if (!node)
continue;
1531 const auto& payload = node->definition().record.data;
1532 if (std::holds_alternative<data::SequenceSoundData>(payload) ||
1533 std::holds_alternative<data::SequenceVideoData>(payload))
1534 node->commandedVolume =
clamped;
1541 std::uint16_t
pitch,
bool wholeTree)
1547 auto* node = find(
match);
1548 if (!node)
continue;
1549 const auto& payload = node->definition().record.data;
1550 if (std::holds_alternative<data::SequenceSoundData>(payload) ||
1551 std::holds_alternative<data::SequenceVideoData>(payload))
1552 node->commandedPitch =
pitch;
1559 std::int8_t
panning,
bool wholeTree)
1563 const auto clamped =
static_cast<std::int8_t
>(
1564 std::clamp<int>(
panning, -100, 100));
1567 auto* node = find(
match);
1568 if (!node)
continue;
1569 const auto& payload = node->definition().record.data;
1570 if (std::holds_alternative<data::SequenceSoundData>(payload) ||
1571 std::holds_alternative<data::SequenceVideoData>(payload))
1572 node->commandedPanning =
clamped;
1583 if (
auto* node = find(
match)) move(*node, transform);
1589 std::uint16_t
priority,
bool wholeTree)
1594 if (
auto* node = find(
match))
1596 node->redrawRequested =
true;
1597 forceAncestors(*node);
1604 const auto* node = find(
id);
1605 if (!node)
return std::nullopt;
1607 node->definition().dataId, node->definition().record.chunk.headerOffset, node->priority,
1608 node->labelNumber, node->clock.clock(), node->clock.endTime(),
1609 node->clock.timeMultiple(), node->clock.paused(),
1610 node->needsRedraw, node->dimensionality, node->definition().contentsDataId, node->explicitlyPositioned,
1611 node->localTransform, node->tweekerTransformApplied,
1612 node->tweekerTransform, node->worldTransform, {}};
1613 view.meshChoice = node->meshChoice;
1616 view.pitch = node->commandedPitch.value_or(node->pitch);
1617 view.volume = node->commandedVolume.value_or(node->volume);
1618 view.panning = node->commandedPanning.value_or(node->panning);
1619 for (
const auto&
child : node->children) view.children.push_back(
child->id);
1623 std::uint16_t
priority,
bool wholeTree)
const
1626 if (
matches.empty())
return std::nullopt;
1627 const auto* node = find(
matches.front());
1628 if (!node)
return std::nullopt;
1630 SequenceInfoView result{node->id, node->clock.clock(), node->clock.endTime(),
1631 node->dimensionality, std::nullopt};
1632 if (node->dimensionality == 3)
1633 if (
const auto*
matrix = std::get_if<Matrix3D>(&node->worldTransform))
1634 result.sequenceToWorldTransformation = *
matrix;
1642 if (
matches.empty())
return true;
1644 const auto* node = find(
matches.front());
1645 if (!node)
return true;
1646 return node->clock.clock() >= node->clock.endTime();
1654 if (
roots.empty())
return std::nullopt;
1655 const auto* selected = find(
roots.front());
1656 if (!selected)
return std::nullopt;
1658 const auto visit = [&](
const auto&
self,
const Node& node) -> std::optional<Matrix3D>
1660 if (node.definition().record.chunk.id == 9 && node.dimensionality == 3)
1661 if (
const auto*
matrix = std::get_if<Matrix3D>(&node.worldTransform))
1663 for (
const auto&
child : node.children)
1665 return std::nullopt;
1672 std::vector<SequenceBitmapInstanceView> result;
1673 const auto visit = [&](
const auto&
self,
const Nodes&
nodes) ->
void {
1674 for (
const auto& node :
nodes)
1676 const auto& definition = node->definition();
1677 if (definition.contentsDataId && node->dimensionality == 2 &&
1678 std::holds_alternative<data::SequenceBitmapData>(definition.record.data) &&
1679 std::holds_alternative<Matrix2D>(node->worldTransform))
1683 result.push_back({node->id, *definition.contentsDataId,
1684 node->priority, node->clock.clock(),
1685 std::get<Matrix2D>(node->worldTransform),
1686 boundingBox2D(definition.attributes), definition.zeroBitmapOffset,
1699 std::vector<SequenceSoundInstanceView> result;
1700 const auto visit = [&](
const auto&
self,
const Nodes&
nodes) ->
void {
1701 for (
const auto& node :
nodes)
1703 const auto& definition = node->definition();
1704 if (std::holds_alternative<data::SequenceSoundData>(
1705 definition.record.data))
1707 std::optional<std::int32_t>
centerX;
1708 if (node->dimensionality == 2 &&
1709 std::holds_alternative<Matrix2D>(node->worldTransform))
1711 definition.attributes,
1712 std::get<Matrix2D>(node->worldTransform));
1715 definition.contentsDataId.value_or(
1718 node->clock.clock(),
1719 node->clock.endingAction(),
1720 node->dimensionality,
1721 node->commandedPitch.value_or(node->pitch),
1722 node->commandedVolume.value_or(node->volume),
1723 node->commandedPanning.value_or(node->panning),
1726 node->clock.paused(), node->seekGeneration, {},
1727 node->commandedVolume, node->commandedPanning});
1730 std::get<Matrix2D>(node->worldTransform),
1744 std::vector<SequenceVideoInstanceView> result;
1745 const auto visit = [&](
const auto&
self,
const Nodes&
nodes) ->
void {
1746 for (
const auto& node :
nodes)
1748 const auto& definition = node->definition();
1749 if (std::holds_alternative<data::SequenceVideoData>(
1750 definition.record.data) &&
1751 node->dimensionality == 2 &&
1752 std::holds_alternative<Matrix2D>(node->worldTransform))
1757 node->clock.clock(),
1758 std::get<data::SequenceVideoData>(definition.record.data),
1761 std::get<Matrix2D>(node->worldTransform),
1762 node->clock.endingAction(), definition.binkDoubleSize,
1763 node->clock.elapsedParentClock(),
1764 node->commandedPitch.value_or(node->pitch),
1765 node->commandedVolume.value_or(node->volume),
1766 node->commandedPanning.value_or(node->panning)});
1777 std::vector<SequenceMeshInstanceView> result;
1778 const auto visit = [&](
const auto&
self,
const Nodes&
nodes) ->
void {
1779 for (
const auto& node :
nodes)
1781 const auto& definition = node->definition();
1782 if ((definition.record.chunk.id == 4 ||
1783 definition.record.chunk.id == 9) &&
1784 definition.contentsDataId &&
1785 node->dimensionality == 3 &&
1786 std::holds_alternative<Matrix3D>(node->worldTransform))
1788 std::optional<data::DataId> textureMapDataId;
1789 std::optional<data::DataId> jointPositionsDataId;
1790 if (
const auto* model =
1791 std::get_if<data::SequenceModelData>(
1792 &definition.record.data))
1796 definition.record.header,
1797 model->textureMapDataId,
1801 definition.record.header,
1802 model->jointPositionsDataId,
1807 while (
root->parent)
1809 const auto rootSequenceDataId =
1812 result.push_back({node->id, *definition.contentsDataId,
1814 node->priority, node->clock.clock(),
1815 std::get<Matrix3D>(node->worldTransform),
1818 definition.attributes, node->dimensionality),
1819 textureMapDataId, jointPositionsDataId,
root->priority});
1828 std::vector<SequenceScrollingWorldView>
1831 std::vector<SequenceScrollingWorldView> result;
1832 const auto visit = [&](
const auto&
self,
const Nodes&
nodes) ->
void {
1833 for (
const auto& node :
nodes)
1835 const auto& definition = node->definition();
1836 if (definition.record.header.scrollingWorld)
1840 node->dimensionality,
1841 node->worldTransform,
1842 node->dimensionality == 2
1846 definition.attributes, node->dimensionality),
1847 node->scrollingOnScreen});
1858 std::vector<SequenceCamera3DView> result;
1859 const auto visit = [&](
const auto&
self,
const Nodes&
nodes) ->
void {
1860 for (
const auto& node :
nodes)
1862 const auto& definition = node->definition();
1863 if (definition.record.chunk.id == 7 && node->dimensionality == 3 &&
1864 std::holds_alternative<Matrix3D>(node->worldTransform))
1866 const auto& camera = std::get<data::SequenceCameraData>(
1867 definition.record.data);
1868 result.push_back({node->id, camera.cameraLabel, node->priority,
1869 node->clock.clock(), std::get<Matrix3D>(node->worldTransform),
1870 node->cameraFieldOfView, camera.nearClipPlaneDistance,
1871 camera.farClipPlaneDistance});
1881 std::uint8_t
label)
const
1883 if (
label == 0)
return std::nullopt;
1884 const auto* node = labelOwners_[
label] ? find(labelOwners_[
label]) :
nullptr;
1885 if (!node || node->dimensionality != 2 ||
1886 !std::holds_alternative<Matrix2D>(node->worldTransform))
1887 return std::nullopt;
1888 const bool camera = node->definition().record.chunk.id == 7;
1890 if (!bounds && camera)
1893 std::get<Matrix2D>(node->worldTransform),
1895 camera ? std::optional<float>{node->cameraFieldOfView} : std::nullopt};
1899 std::uint8_t
label)
const
1901 if (
label == 0)
return std::nullopt;
1902 const auto owner = labelOwners_[
label];
1903 const auto* node = owner != 0 ? find(owner) :
nullptr;
1904 if (!node || node->dimensionality != 3 ||
1905 !std::holds_alternative<Matrix3D>(node->worldTransform))
1906 return std::nullopt;
1911 const auto* camera = std::get_if<data::SequenceCameraData>(
1912 &node->definition().record.data);
1914 node->clock.clock(), std::get<Matrix3D>(node->worldTransform),
1915 camera ? node->cameraFieldOfView : 0.7853981633974F,
1916 camera ? camera->nearClipPlaneDistance : 1.0F,
1917 camera ? camera->farClipPlaneDistance : 5000.0F};
1922 std::vector<SequenceNodeId> result;
1923 for (
const auto&
root : roots_) result.push_back(
root->id);
std::vector< std::uint8_t > data
std::atomic< float > left
std::atomic< float > right
LegacyBitmapMetadata metadata
std::vector< std::byte > bytes
std::shared_ptr< const data::HmdTextureImage > source
static std::expected< SequenceChildSchedule, ChildScheduleError > read(data::SharedDataBytes bytes, data::DataId containingDataId, std::size_t maximumRecords=65 '536)
static std::expected< SequenceClock, ClockError > start(const data::LegacySequenceRecord &record, ClockStartOptions options={})
std::span< const SequenceDescription > descriptions() const noexcept
std::shared_ptr< const data::ResourceSnapshot > resources() const noexcept
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > rawBitmap(data::DataId id, data::LegacyDataType sourceType, DescriptionLimits limits={})
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > load(const data::DataSource &source, data::DataId id, std::size_t offset=0, DescriptionLimits limits={})
static std::expected< std::shared_ptr< const SequenceProgram >, RuntimeError > runtimeVideo(data::DataId id, std::string fileName, data::SequenceVideoData options, data::Sequence2DBoundingBoxAttribute bounds, bool binkDoubleSize=false)
std::expected< void, RuntimeError > setEndingAction(SequenceNodeId node, std::uint8_t action)
std::expected< void, RuntimeError > setVolume(SequenceNodeId node, std::uint8_t volume)
SequenceRuntime(RuntimeLimits limits={})
std::vector< SequenceScrollingWorldView > scrollingWorldInstances() const
bool isSequenceFinished(data::DataId id, std::uint16_t priority, bool wholeTree=false) const
std::vector< SequenceVideoInstanceView > videoInstances() const
std::vector< SequenceNodeId > matching(data::DataId id, std::uint16_t priority, bool wholeTree=false) const
std::size_t setPitchMatching(data::DataId id, std::uint16_t priority, std::uint16_t pitch, bool wholeTree=false)
std::expected< void, RuntimeError > requestSoundClock(SequenceNodeId id, std::int32_t mediaClock)
std::size_t forceRedrawMatching(data::DataId id, std::uint16_t priority, bool wholeTree=false)
std::expected< void, RuntimeError > setPaused(SequenceNodeId node, bool paused)
std::expected< void, RuntimeError > update(std::int32_t parentClock)
std::expected< void, RuntimeError > requestVideoClock(SequenceNodeId node, std::int32_t mediaClock, std::int32_t duration, bool ended)
std::optional< Matrix3D > childMeshWorldMatrix(data::DataId id, std::uint16_t priority) const
std::size_t moveMatching(data::DataId id, std::uint16_t priority, const SequenceTransform &transform, bool wholeTree=false)
std::expected< void, RuntimeError > stop(SequenceNodeId node)
std::optional< SequenceCamera2DView > camera2DForLabel(std::uint8_t label) const
std::size_t setPanningMatching(data::DataId id, std::uint16_t priority, std::int8_t panning, bool wholeTree=false)
std::expected< void, RuntimeError > setPitch(SequenceNodeId node, std::uint16_t pitch)
std::size_t stopMatching(data::DataId id, std::uint16_t priority, bool wholeTree=false)
std::vector< SequenceNodeId > roots() const
std::expected< std::size_t, RuntimeError > setEndingActionMatching(data::DataId id, std::uint16_t priority, std::uint8_t action, bool wholeTree=false)
std::vector< SequenceBitmapInstanceView > bitmapInstances() const
std::expected< SequenceNodeId, RuntimeError > start(std::shared_ptr< const SequenceProgram > program, std::uint16_t priority=0, ClockStartOptions options={}, std::optional< SequenceTransform > initialTransform=std::nullopt, std::uint8_t labelOverride=0)
std::vector< SequenceSoundInstanceView > soundInstances() const
std::size_t liveNodeCount() const noexcept
std::size_t setVolumeMatching(data::DataId id, std::uint16_t priority, std::uint8_t volume, bool wholeTree=false)
std::span< const SequenceEvent > events() const noexcept
std::expected< void, RuntimeError > requestSoundFailure(SequenceNodeId id)
std::expected< void, RuntimeError > setScrollingWorldVisibility(SequenceNodeId node, bool onScreen)
std::vector< SequenceCamera3DView > cameraInstances() const
std::vector< SequenceMeshInstanceView > meshInstances() const
std::optional< SequenceInfoView > info(data::DataId id, std::uint16_t priority, bool wholeTree=false) const
std::expected< void, RuntimeError > setPanning(SequenceNodeId node, std::int8_t panning)
std::expected< void, RuntimeError > seek(SequenceNodeId node, std::int32_t time)
std::optional< SequenceCamera3DView > cameraForLabel(std::uint8_t label) const
std::optional< SequenceNodeView > inspect(SequenceNodeId node) const
std::uint32_t legacyWaveDurationTicks(std::span< const std::uint8_t > riffWave, std::uint32_t ticksPerSecond) noexcept
constexpr DataId EmptyDataId
std::expected< LegacySequenceAttributes, SequenceError > readLegacySequenceAttributes(const LegacyChunkReader &reader, std::size_t maximumAttributes)
std::shared_ptr< const DataBytes > SharedDataBytes
std::expected< LegacySequenceRecord, SequenceError > readLegacySequenceRecord(LegacyChunkReader &reader)
std::expected< LegacyChunkReader, DataError > openLegacyChunkReader(const DataSource &source, DataId id)
constexpr DataId resolveSequenceDataId(const LegacySequenceHeader &header, DataId rawId, DataId containingSequenceId) noexcept
const PendingPhase & current(const GameState &state)
std::expected< EvaluatedTweekerTransform, TweekerTransformError > evaluateTweekerTransform(const data::LegacySequenceAttributes &attributes, std::uint8_t interpolationType, std::int32_t clock, std::int32_t endTime, std::uint8_t parentDimensionality) noexcept
std::expected< SequenceChildSchedule, ChildScheduleError > openSequenceChildSchedule(const data::DataSource &source, data::DataId parentDataId, std::size_t parentOffset, std::size_t maximumRecords)
Matrix2D identity2D() noexcept
std::variant< std::monostate, Matrix2D, Matrix3D > SequenceTransform
TweekerKeys selectTweekerKeys(const data::LegacySequenceAttributes &attributes) noexcept
std::uint64_t SequenceNodeId
SequenceTransform composeSequenceWorld(const SequenceTransform &local, std::uint8_t dimensionality, const SequenceTransform &parentWorld, std::uint8_t parentDimensionality) noexcept
Matrix3D identity3D() noexcept
@ ParentClockWentBackwards
InitialSequenceTransform initialSequenceTransform(const data::LegacySequenceRecord &record, const data::LegacySequenceAttributes &attributes, std::uint8_t parentDimensionality) noexcept
Matrix2D multiply(const Matrix2D &left, const Matrix2D &right) noexcept
std::shared_ptr< const data::ResourceSnapshot > resources() noexcept
std::vector< LegacySequenceAttribute > values
std::size_t maximumReferences
std::size_t maximumDescriptions
std::size_t maximumBirthsPerOperation
std::size_t maximumLiveNodes
std::optional< std::uint8_t > commandedVolume
data::SharedDataBytes preloadedData
bool scrollingHibernating
SequenceTransform worldTransform
std::uint64_t seekGeneration
std::shared_ptr< const SequenceProgram > program
SequenceTransform localTransform
SequenceMeshChoice3D meshChoice
std::optional< std::int8_t > commandedPanning
std::uint64_t movementRevision
std::optional< std::uint16_t > commandedPitch
std::uint8_t dimensionality
const SequenceDescription & definition() const
bool explicitlyPositioned
SequenceChildSchedule schedule
std::optional< float > tweekerProportion
SequenceTransform tweekerTransform
bool tweekerTransformApplied
std::uint64_t movementRevision