libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
datastream.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#if PROMEKI_ENABLE_CORE
12#include <cstdint>
13#include <cstring>
14#include <promeki/config.h>
15#include <promeki/namespace.h>
16#include <promeki/string.h>
17#include <promeki/buffer.h>
18#include <promeki/list.h>
19#include <promeki/map.h>
20#include <promeki/set.h>
21#include <promeki/hashmap.h>
22#include <promeki/hashset.h>
23#include <promeki/rect.h>
24#include <promeki/point.h>
25#include <promeki/size2d.h>
26#include <promeki/rational.h>
27#include <promeki/result.h>
28#include <promeki/iodevice.h>
29
30PROMEKI_NAMESPACE_BEGIN
31
41enum DataTypeID : uint16_t;
42
43class Variant;
44
127class DataStream {
128 public:
130 enum ByteOrder {
131 BigEndian,
132 LittleEndian
133 };
134
136 enum Status {
137 Ok,
138 ReadPastEnd,
139 ReadCorruptData,
140 WriteFailed
141 };
142
150 static constexpr uint16_t CurrentVersion = 3;
151
153 static constexpr size_t HeaderSize = 16;
154
157 static constexpr size_t FrameHeaderSize = 8;
158
163 static constexpr uint32_t MaxFrameBodySize = 1u << 30; // 1 GiB
164
166 static constexpr uint8_t Magic[4] = {0x50, 0x4D, 0x44, 0x53};
167
179 static DataStream createWriter(IODevice *device, ByteOrder order = BigEndian);
180
190 static DataStream createReader(IODevice *device);
191
202 explicit DataStream(IODevice *device);
203
205 ~DataStream() = default;
206
207 // ============================================================
208 // Byte order
209 // ============================================================
210
212 void setByteOrder(ByteOrder order) { _byteOrder = order; }
213
215 ByteOrder byteOrder() const { return _byteOrder; }
216
217 // ============================================================
218 // Version
219 // ============================================================
220
228 uint16_t version() const { return _version; }
229
230 // ============================================================
231 // Status and error handling
232 // ============================================================
233
235 Status status() const { return _status; }
236
240 const String &errorContext() const { return _errorContext; }
241
245 Error toError() const;
246
250 void resetStatus();
251
255 bool atEnd() const;
256
258 IODevice *device() const { return _device; }
259
260 // ============================================================
261 // Write operators — primitives
262 // ============================================================
263
265 DataStream &operator<<(int8_t val);
267 DataStream &operator<<(uint8_t val);
269 DataStream &operator<<(int16_t val);
271 DataStream &operator<<(uint16_t val);
273 DataStream &operator<<(int32_t val);
275 DataStream &operator<<(uint32_t val);
277 DataStream &operator<<(int64_t val);
279 DataStream &operator<<(uint64_t val);
281 DataStream &operator<<(float val);
283 DataStream &operator<<(double val);
285 DataStream &operator<<(bool val);
287 DataStream &operator<<(const String &val);
289 DataStream &operator<<(const Buffer &val);
299 DataStream &operator<<(const Variant &val);
300
301 // ============================================================
302 // Read operators — primitives
303 // ============================================================
304
306 DataStream &operator>>(int8_t &val);
308 DataStream &operator>>(uint8_t &val);
310 DataStream &operator>>(int16_t &val);
312 DataStream &operator>>(uint16_t &val);
314 DataStream &operator>>(int32_t &val);
316 DataStream &operator>>(uint32_t &val);
318 DataStream &operator>>(int64_t &val);
320 DataStream &operator>>(uint64_t &val);
322 DataStream &operator>>(float &val);
324 DataStream &operator>>(double &val);
326 DataStream &operator>>(bool &val);
328 DataStream &operator>>(String &val);
330 DataStream &operator>>(Buffer &val);
340 DataStream &operator>>(Variant &val);
341
342 // ============================================================
343 // Result<T>-returning read API
344 // ============================================================
345
351 template <typename T> Result<T> read() {
352 T val{};
353 *this >> val;
354 if (_status != Ok) return makeError<T>(toError());
355 return makeResult(std::move(val));
356 }
357
358 // ============================================================
359 // Extension API (for implementing custom operator<< / >>)
360 // ============================================================
361
380 void beginFrame(DataTypeID id, uint16_t version);
381
389 void endFrame();
390
410 bool readFrame(DataTypeID expected, uint16_t maxVersion = 1,
411 uint16_t *outVersion = nullptr, uint32_t *outSize = nullptr);
412
421 bool readFrameHeader(DataTypeID &outTag, uint16_t &outVersion, uint32_t &outSize);
422
447 bool peekFrameHeader(DataTypeID &outTag, uint16_t &outVersion, uint32_t &outSize);
448
458 void skipFrame();
459
470 void setError(Status s, String ctx);
471
472 // ============================================================
473 // Raw byte access (untagged escape hatch)
474 // ============================================================
475
477 ssize_t readRawData(void *buf, size_t len);
478
487 ssize_t writeRawData(const void *buf, size_t len);
488
490 ssize_t skipRawData(size_t len);
491
492 private:
493 void writeHeader();
494 void readHeader();
495
500 bool readBytes(void *buf, size_t len);
501
506 bool skipFrameBody(uint32_t sz);
507
512 bool writeBytes(const void *buf, size_t len);
513
517 template <typename T> void swapIfNeeded(T &val) const {
518 if constexpr (sizeof(T) == 1) return;
519 if (_byteOrder == nativeByteOrder()) return;
520 uint8_t *p = reinterpret_cast<uint8_t *>(&val);
521 if constexpr (sizeof(T) == 2) {
522 std::swap(p[0], p[1]);
523 } else if constexpr (sizeof(T) == 4) {
524 std::swap(p[0], p[3]);
525 std::swap(p[1], p[2]);
526 } else if constexpr (sizeof(T) == 8) {
527 std::swap(p[0], p[7]);
528 std::swap(p[1], p[6]);
529 std::swap(p[2], p[5]);
530 std::swap(p[3], p[4]);
531 }
532 }
533
535 static ByteOrder nativeByteOrder() {
536 static const uint16_t val = 1;
537 return (*reinterpret_cast<const uint8_t *>(&val) == 1) ? LittleEndian : BigEndian;
538 }
539
543 struct PendingFrame {
544 DataTypeID tag;
545 uint16_t version;
546 List<uint8_t> body;
547 };
548
549 IODevice * _device = nullptr;
550 ByteOrder _byteOrder = BigEndian;
551 uint16_t _version = 0;
552 Status _status = Ok;
553 String _errorContext;
554 List<PendingFrame> _frameStack;
555
556 // One-deep frame-header lookahead. Populated by
557 // @ref peekFrameHeader and drained by the next
558 // @ref readFrameHeader so that Variant dispatch can
559 // inspect the tag without requiring a seekable device.
560 bool _peekedHeaderValid = false;
561 DataTypeID _peekedTag;
562 uint16_t _peekedVersion = 0;
563 uint32_t _peekedSize = 0;
564};
565
566// ============================================================================
567// Container template operators
568// ============================================================================
569//
570// These let arbitrary List<T>, Map<K,V>, and Set<T> flow through a DataStream
571// as long as T (and K, V) already have operator<< / operator>> overloads.
572// They write a tag (DataTypeList / DataTypeMap / DataTypeSet) followed by a
573// uint32_t count and then the fully-tagged elements.
574
575namespace detail {
578 inline constexpr uint32_t DataStreamMaxContainerCount = 256u * 1024u * 1024u;
579} // namespace detail
580
581PROMEKI_NAMESPACE_END
582
583// The container / geometry operator templates below need DataTypeID
584// values (DataTypeList, DataTypeSize2D, ...) by name, which means they
585// need the full enum definition. Pull in datatype.h here.
586//
587// enum.h is included AFTER datatype.h so the slicing operator<< / >>
588// overloads for @ref TypedEnum subclasses can resolve the underlying
589// Enum::promekiDataType inside their body. Order matters: enum.h
590// itself includes datatype.h, but datatype.h is already loaded by the
591// time we get here, so the second inclusion is a no-op.
592#include <promeki/datatype.h>
593#include <promeki/enum.h>
594// json.h is pulled in here — after DataStream is complete but before
595// the @ref Detail::makeDefaultOps definition below — so the
596// @c toJson / @c fromJson Ops slots can be populated from lambdas
597// whose bodies reference @ref JsonObject directly. json.h re-includes
598// datastream.h but the pragma-once guard makes the inner include a
599// no-op; by this point in this header DataStream is fully declared,
600// which is everything json.h's bottom-half operators need.
601#include <promeki/json.h>
602
603PROMEKI_NAMESPACE_BEGIN
604
605// ============================================================================
606// Free-function DataStream operator allowlist (geometry templates only)
607// ============================================================================
608//
609// Size2DTemplate<T> and Rational<T> ship with free-function operator<< /
610// operator>> templates rather than a member API. Flagging them here
611// lets @ref Detail::makeDefaultOps populate the writeStream / readStream
612// slots through the free-function path. These specializations live in
613// the header (rather than in variant.cpp) so they are visible at every
614// makeDefaultOps instantiation site — including the call sites in
615// datatype.cpp's registerBuiltinDataTypes.
616
617namespace Detail {
618
619template <typename T> struct HasFreeDataStreamWrite<Size2DTemplate<T>> : std::true_type {};
620template <typename T> struct HasFreeDataStreamRead<Size2DTemplate<T>> : std::true_type {};
621template <typename T> struct HasFreeDataStreamWrite<Rational<T>> : std::true_type {};
622template <typename T> struct HasFreeDataStreamRead<Rational<T>> : std::true_type {};
623
624} // namespace Detail
625
626// ============================================================================
627// Geometry template operators
628// ============================================================================
629//
630// These share a single tag per kind (DataTypeSize2D, DataTypeRect, DataTypePoint,
631// DataTypeRational) and rely on the inner values being written/read via their
632// own primitive operators, which means the inner values carry their own
633// type tags. This lets one Size2DTemplate<T> template cover uint32_t,
634// int32_t, float, etc. transparently.
635
639template <typename T> DataStream &operator<<(DataStream &stream, const Size2DTemplate<T> &sz) {
640 stream.beginFrame(DataTypeSize2D, 1);
641 stream << sz.width() << sz.height();
642 stream.endFrame();
643 return stream;
644}
645
649template <typename T> DataStream &operator>>(DataStream &stream, Size2DTemplate<T> &sz) {
650 if (!stream.readFrame(DataTypeSize2D)) {
651 sz = Size2DTemplate<T>();
652 return stream;
653 }
654 T w{}, h{};
655 stream >> w >> h;
656 if (stream.status() != DataStream::Ok) {
657 sz = Size2DTemplate<T>();
658 return stream;
659 }
660 sz = Size2DTemplate<T>(w, h);
661 return stream;
662}
663
667template <typename T> DataStream &operator<<(DataStream &stream, const Rational<T> &r) {
668 stream.beginFrame(DataTypeRational, 1);
669 stream << r.numerator() << r.denominator();
670 stream.endFrame();
671 return stream;
672}
673
677template <typename T> DataStream &operator>>(DataStream &stream, Rational<T> &r) {
678 if (!stream.readFrame(DataTypeRational)) {
679 r = Rational<T>();
680 return stream;
681 }
682 T num{}, den{1};
683 stream >> num >> den;
684 if (stream.status() != DataStream::Ok) {
685 r = Rational<T>();
686 return stream;
687 }
688 r = Rational<T>(num, den);
689 return stream;
690}
691
695template <typename T> DataStream &operator<<(DataStream &stream, const Rect<T> &rect) {
696 stream.beginFrame(DataTypeRect, 1);
697 stream << rect.x() << rect.y() << rect.width() << rect.height();
698 stream.endFrame();
699 return stream;
700}
701
705template <typename T> DataStream &operator>>(DataStream &stream, Rect<T> &rect) {
706 if (!stream.readFrame(DataTypeRect)) {
707 rect = Rect<T>();
708 return stream;
709 }
710 T x{}, y{}, w{}, h{};
711 stream >> x >> y >> w >> h;
712 if (stream.status() != DataStream::Ok) {
713 rect = Rect<T>();
714 return stream;
715 }
716 rect = Rect<T>(x, y, w, h);
717 return stream;
718}
719
723template <typename T, size_t N> DataStream &operator<<(DataStream &stream, const Point<T, N> &point) {
724 stream.beginFrame(DataTypePoint, 1);
725 stream << static_cast<uint32_t>(N);
726 const Array<T, N> &arr = point;
727 for (size_t i = 0; i < N; ++i) stream << arr[i];
728 stream.endFrame();
729 return stream;
730}
731
735template <typename T, size_t N> DataStream &operator>>(DataStream &stream, Point<T, N> &point) {
736 if (!stream.readFrame(DataTypePoint)) {
737 point = Point<T, N>();
738 return stream;
739 }
740 uint32_t dims = 0;
741 stream >> dims;
742 if (stream.status() != DataStream::Ok) {
743 point = Point<T, N>();
744 return stream;
745 }
746 if (dims != N) {
747 stream.setError(DataStream::ReadCorruptData,
748 String::sprintf("Point dimension mismatch: expected %zu, got %u", N,
749 static_cast<unsigned>(dims)));
750 point = Point<T, N>();
751 return stream;
752 }
753 Array<T, N> arr;
754 for (size_t i = 0; i < N; ++i) {
755 T val{};
756 stream >> val;
757 if (stream.status() != DataStream::Ok) {
758 point = Point<T, N>();
759 return stream;
760 }
761 arr[i] = val;
762 }
763 point = Point<T, N>(arr);
764 return stream;
765}
766
770template <typename T> DataStream &operator<<(DataStream &stream, const List<T> &list) {
771 stream.beginFrame(DataTypeList, 1);
772 stream << static_cast<uint32_t>(list.size());
773 for (const auto &item : list) stream << item;
774 stream.endFrame();
775 return stream;
776}
777
781template <typename T> DataStream &operator>>(DataStream &stream, List<T> &list) {
782 list.clear();
783 if (!stream.readFrame(DataTypeList)) return stream;
784 uint32_t count = 0;
785 stream >> count;
786 if (stream.status() != DataStream::Ok) return stream;
787 if (count > detail::DataStreamMaxContainerCount) {
788 stream.setError(DataStream::ReadCorruptData, String("List element count exceeds sanity limit"));
789 return stream;
790 }
791 list.reserve(count);
792 for (uint32_t i = 0; i < count; ++i) {
793 T item{};
794 stream >> item;
795 if (stream.status() != DataStream::Ok) return stream;
796 list.pushToBack(std::move(item));
797 }
798 return stream;
799}
800
804template <typename K, typename V> DataStream &operator<<(DataStream &stream, const Map<K, V> &map) {
805 stream.beginFrame(DataTypeMap, 1);
806 stream << static_cast<uint32_t>(map.size());
807 for (auto it = map.cbegin(); it != map.cend(); ++it) {
808 stream << it->first << it->second;
809 }
810 stream.endFrame();
811 return stream;
812}
813
817template <typename K, typename V> DataStream &operator>>(DataStream &stream, Map<K, V> &map) {
818 map.clear();
819 if (!stream.readFrame(DataTypeMap)) return stream;
820 uint32_t count = 0;
821 stream >> count;
822 if (stream.status() != DataStream::Ok) return stream;
823 if (count > detail::DataStreamMaxContainerCount) {
824 stream.setError(DataStream::ReadCorruptData, String("Map entry count exceeds sanity limit"));
825 return stream;
826 }
827 for (uint32_t i = 0; i < count; ++i) {
828 K key{};
829 V value{};
830 stream >> key >> value;
831 if (stream.status() != DataStream::Ok) return stream;
832 map.insert(std::move(key), std::move(value));
833 }
834 return stream;
835}
836
840template <typename T> DataStream &operator<<(DataStream &stream, const Set<T> &set) {
841 stream.beginFrame(DataTypeSet, 1);
842 stream << static_cast<uint32_t>(set.size());
843 for (const auto &item : set) stream << item;
844 stream.endFrame();
845 return stream;
846}
847
851template <typename T> DataStream &operator>>(DataStream &stream, Set<T> &set) {
852 set.clear();
853 if (!stream.readFrame(DataTypeSet)) return stream;
854 uint32_t count = 0;
855 stream >> count;
856 if (stream.status() != DataStream::Ok) return stream;
857 if (count > detail::DataStreamMaxContainerCount) {
858 stream.setError(DataStream::ReadCorruptData, String("Set element count exceeds sanity limit"));
859 return stream;
860 }
861 for (uint32_t i = 0; i < count; ++i) {
862 T item{};
863 stream >> item;
864 if (stream.status() != DataStream::Ok) return stream;
865 set.insert(std::move(item));
866 }
867 return stream;
868}
869
873template <typename K, typename V> DataStream &operator<<(DataStream &stream, const HashMap<K, V> &map) {
874 stream.beginFrame(DataTypeHashMap, 1);
875 stream << static_cast<uint32_t>(map.size());
876 for (auto it = map.cbegin(); it != map.cend(); ++it) {
877 stream << it->first << it->second;
878 }
879 stream.endFrame();
880 return stream;
881}
882
890template <typename K, typename V> DataStream &operator>>(DataStream &stream, HashMap<K, V> &map) {
891 map.clear();
892 if (!stream.readFrame(DataTypeHashMap)) return stream;
893 uint32_t count = 0;
894 stream >> count;
895 if (stream.status() != DataStream::Ok) return stream;
896 if (count > detail::DataStreamMaxContainerCount) {
897 stream.setError(DataStream::ReadCorruptData, String("HashMap entry count exceeds sanity limit"));
898 return stream;
899 }
900 for (uint32_t i = 0; i < count; ++i) {
901 K key{};
902 V value{};
903 stream >> key >> value;
904 if (stream.status() != DataStream::Ok) return stream;
905 map.insert(std::move(key), std::move(value));
906 }
907 return stream;
908}
909
913template <typename T> DataStream &operator<<(DataStream &stream, const HashSet<T> &set) {
914 stream.beginFrame(DataTypeHashSet, 1);
915 stream << static_cast<uint32_t>(set.size());
916 for (const auto &item : set) stream << item;
917 stream.endFrame();
918 return stream;
919}
920
924template <typename T> DataStream &operator>>(DataStream &stream, HashSet<T> &set) {
925 set.clear();
926 if (!stream.readFrame(DataTypeHashSet)) return stream;
927 uint32_t count = 0;
928 stream >> count;
929 if (stream.status() != DataStream::Ok) return stream;
930 if (count > detail::DataStreamMaxContainerCount) {
931 stream.setError(DataStream::ReadCorruptData, String("HashSet element count exceeds sanity limit"));
932 return stream;
933 }
934 for (uint32_t i = 0; i < count; ++i) {
935 T item{};
936 stream >> item;
937 if (stream.status() != DataStream::Ok) return stream;
938 set.insert(std::move(item));
939 }
940 return stream;
941}
942
943// ============================================================================
944// DataStream <<, >> for JsonObject / JsonArray
945//
946// These live here (rather than in @c json.h) so @c json.h can stay
947// free of a @c datastream.h dependency — which is what lets the
948// @ref Detail::makeDefaultOps populator below reference @ref JsonObject
949// directly without forming a circular include.
950//
951// Wire form (v1): compact JSON text as a tagged, length-prefixed
952// String body. Readers that see a truncated or malformed payload
953// flag the stream @c ReadCorruptData.
954// ============================================================================
955
956inline DataStream &operator<<(DataStream &stream, const JsonObject &obj) {
957 stream.beginFrame(DataTypeJsonObject, 1);
958 stream << obj.toString(0);
959 stream.endFrame();
960 return stream;
961}
962
963inline DataStream &operator>>(DataStream &stream, JsonObject &obj) {
964 if (!stream.readFrame(DataTypeJsonObject)) {
965 obj = JsonObject();
966 return stream;
967 }
968 String text;
969 stream >> text;
970 if (stream.status() != DataStream::Ok) {
971 obj = JsonObject();
972 return stream;
973 }
974 Error err;
975 obj = JsonObject::parse(text, &err);
976 if (err.isError()) {
977 stream.setError(DataStream::ReadCorruptData, String("JsonObject::parse failed"));
978 }
979 return stream;
980}
981
982inline DataStream &operator<<(DataStream &stream, const JsonArray &arr) {
983 stream.beginFrame(DataTypeJsonArray, 1);
984 stream << arr.toString(0);
985 stream.endFrame();
986 return stream;
987}
988
989inline DataStream &operator>>(DataStream &stream, JsonArray &arr) {
990 if (!stream.readFrame(DataTypeJsonArray)) {
991 arr = JsonArray();
992 return stream;
993 }
994 String text;
995 stream >> text;
996 if (stream.status() != DataStream::Ok) {
997 arr = JsonArray();
998 return stream;
999 }
1000 Error err;
1001 arr = JsonArray::parse(text, &err);
1002 if (err.isError()) {
1003 stream.setError(DataStream::ReadCorruptData, String("JsonArray::parse failed"));
1004 }
1005 return stream;
1006}
1007
1008namespace Detail {
1009template <> struct HasFreeDataStreamWrite<JsonObject> : std::true_type {};
1010template <> struct HasFreeDataStreamRead<JsonObject> : std::true_type {};
1011template <> struct HasFreeDataStreamWrite<JsonArray> : std::true_type {};
1012template <> struct HasFreeDataStreamRead<JsonArray> : std::true_type {};
1013} // namespace Detail
1014
1015// ============================================================================
1016// Concept detection + ops-table population that requires DataStream
1017// to be complete.
1018//
1019// These live here (rather than in @c datatype.h) because @c datatype.h
1020// is included by many types that themselves get pulled in transitively
1021// from this file (Buffer, MemSpace, StringList, ...). Cycling through
1022// @c datatype.h → @c datastream.h would re-enter mid-flight and break
1023// dependent types' declarations.
1024// ============================================================================
1025
1026namespace Detail {
1027
1031template <typename T>
1032concept HasMemberWriteToStream = requires(const T &t, DataStream &s) {
1033 { t.writeToStream(s) } -> std::convertible_to<Error>;
1034};
1035
1039template <typename T>
1040concept HasMemberReadFromStream = requires(DataStream &s) {
1041 { T::template readFromStream<1>(s) } -> std::convertible_to<Result<T>>;
1042};
1043
1050template <typename T>
1051struct ExactDataStreamWrite<T, std::void_t<decltype(static_cast<DataStream &(DataStream::*)(const T &)>(
1052 &DataStream::operator<<))>> : std::true_type {};
1053
1054template <typename T>
1055struct ExactDataStreamWrite<T, std::void_t<decltype(static_cast<DataStream &(DataStream::*)(T)>(
1056 &DataStream::operator<<))>> : std::true_type {};
1057
1061template <typename T>
1062struct ExactDataStreamRead<T, std::void_t<decltype(static_cast<DataStream &(DataStream::*)(T &)>(
1063 &DataStream::operator>>))>> : std::true_type {};
1064
1069template <typename T>
1070DataType::Ops makeDefaultOps() {
1071 DataType::Ops ops;
1072 if constexpr (std::is_default_constructible_v<T>) {
1073 ops.defaultConstruct = [](void *p) {
1074 ::new (p) T();
1075 return;
1076 };
1077 }
1078 ops.copyConstruct = [](void *dst, const void *src) {
1079 ::new (dst) T(*static_cast<const T *>(src));
1080 return;
1081 };
1082 ops.moveConstruct = [](void *dst, void *src) {
1083 ::new (dst) T(std::move(*static_cast<T *>(src)));
1084 return;
1085 };
1086 ops.destroy = [](void *p) {
1087 static_cast<T *>(p)->~T();
1088 return;
1089 };
1090 if constexpr (HasEqualityOp<T>) {
1091 ops.equal = [](const void *a, const void *b) -> bool {
1092 return *static_cast<const T *>(a) == *static_cast<const T *>(b);
1093 };
1094 }
1095 if constexpr (HasMemberToString<T>) {
1096 ops.toString = [](const void *p, Error *err) -> String {
1097 if (err != nullptr) *err = Error::Ok;
1098 return static_cast<const T *>(p)->toString();
1099 };
1100 }
1101 if constexpr (HasResultFromString<T>) {
1102 ops.fromString = [](const String &s, void *out, Error *err) -> bool {
1103 auto r = T::fromString(s);
1104 if (r.second().isError()) {
1105 if (err != nullptr) *err = r.second();
1106 return false;
1107 }
1108 *static_cast<T *>(out) = r.first();
1109 if (err != nullptr) *err = Error::Ok;
1110 return true;
1111 };
1112 }
1113 if constexpr (HasMemberValueInt<T>) {
1114 // FrameNumber / FrameCount / Enum — value() returns an
1115 // integral so the Variant <-> integer path can fall
1116 // straight through without a type-specific branch.
1117 ops.toInt = [](const void *p, Error *err) -> int64_t {
1118 if (err != nullptr) *err = Error::Ok;
1119 return static_cast<int64_t>(static_cast<const T *>(p)->value());
1120 };
1121 } else if constexpr (HasMemberIdInt<T>) {
1122 // TypeRegistry wrappers (ColorModel, MemSpace,
1123 // PixelFormat, ...) — id() is the canonical integer
1124 // form of the wrapper.
1125 ops.toInt = [](const void *p, Error *err) -> int64_t {
1126 if (err != nullptr) *err = Error::Ok;
1127 return static_cast<int64_t>(static_cast<const T *>(p)->id());
1128 };
1129 }
1130 if constexpr (HasIdCtor<T>) {
1131 ops.fromInt = [](int64_t v, void *out, Error *err) -> bool {
1132 if (err != nullptr) *err = Error::Ok;
1133 *static_cast<T *>(out) = T(static_cast<typename T::ID>(v));
1134 return true;
1135 };
1136 } else if constexpr (HasInt64Ctor<T>) {
1137 ops.fromInt = [](int64_t v, void *out, Error *err) -> bool {
1138 if (err != nullptr) *err = Error::Ok;
1139 *static_cast<T *>(out) = T(v);
1140 return true;
1141 };
1142 }
1143 if constexpr (HasMemberToDouble<T>) {
1144 ops.toFloat = [](const void *p, Error *err) -> double {
1145 if (err != nullptr) *err = Error::Ok;
1146 return static_cast<const T *>(p)->toDouble();
1147 };
1148 }
1149 if constexpr (HasResultFromDouble<T>) {
1150 ops.fromFloat = [](double v, void *out, Error *err) -> bool {
1151 auto r = T::fromDouble(v);
1152 if (r.second().isError()) {
1153 if (err != nullptr) *err = r.second();
1154 return false;
1155 }
1156 *static_cast<T *>(out) = r.first();
1157 if (err != nullptr) *err = Error::Ok;
1158 return true;
1159 };
1160 }
1161 if constexpr (HasMemberToJson<T>) {
1162 ops.toJson = +[](const void *p, Error *err) -> JsonObject {
1163 if (err != nullptr) *err = Error::Ok;
1164 return static_cast<const T *>(p)->toJson();
1165 };
1166 }
1167 if constexpr (HasResultFromJson<T>) {
1168 ops.fromJson = +[](const JsonObject &j, void *out, Error *err) -> bool {
1169 auto r = T::fromJson(j);
1170 if (r.second().isError()) {
1171 if (err != nullptr) *err = r.second();
1172 return false;
1173 }
1174 *static_cast<T *>(out) = r.first();
1175 if (err != nullptr) *err = Error::Ok;
1176 return true;
1177 };
1178 }
1179 if constexpr (HasMemberWriteToStream<T> && HasPromekiDataType<T>) {
1180 // PROMEKI_DATATYPE member-API path: wrap writeToStream
1181 // in a frame using the macro's pinned id + version.
1182 ops.writeStream = [](DataStream &s, const void *p) {
1183 const T &val = *static_cast<const T *>(p);
1184 s.beginFrame(T::promekiDataType::id, T::promekiDataType::version);
1185 Error err = val.writeToStream(s);
1186 s.endFrame();
1187 if (err.isError()) {
1188 if (s.status() == DataStream::Ok) {
1189 s.setError(DataStream::WriteFailed,
1190 String("writeToStream returned ") + err.name());
1191 }
1192 }
1193 return;
1194 };
1195 } else if constexpr (HasDataStreamWriteV<T>) {
1196 // Free-function / member-on-DataStream operator<<: the
1197 // operator already handles its own framing.
1198 ops.writeStream = [](DataStream &s, const void *p) {
1199 s << *static_cast<const T *>(p);
1200 return;
1201 };
1202 }
1203 if constexpr (HasMemberReadFromStream<T> && HasPromekiDataType<T>) {
1204 // PROMEKI_DATATYPE member-API read path: validate frame
1205 // header, then dispatch to the matching readFromStream<V>
1206 // via the macro-generated dispatch table.
1207 ops.readStream = [](DataStream &s, void *p) {
1208 uint16_t ver = 0;
1209 if (!s.readFrame(T::promekiDataType::id, T::promekiDataType::version, &ver,
1210 nullptr)) {
1211 *static_cast<T *>(p) = T();
1212 return;
1213 }
1214 Result<T> r = T::promekiDataType::dispatchRead(s, ver);
1215 if (r.second().isError()) {
1216 if (s.status() == DataStream::Ok) {
1217 s.setError(DataStream::ReadCorruptData,
1218 String("readFromStream returned ") + r.second().name());
1219 }
1220 *static_cast<T *>(p) = T();
1221 return;
1222 }
1223 *static_cast<T *>(p) = std::move(r.first());
1224 return;
1225 };
1226 } else if constexpr (HasDataStreamReadV<T>) {
1227 ops.readStream = [](DataStream &s, void *p) {
1228 s >> *static_cast<T *>(p);
1229 return;
1230 };
1231 }
1232 return ops;
1233}
1234
1235} // namespace Detail
1236
1237// Forward-declare Enum so the constraint below can refer to it without
1238// forcing <promeki/enum.h> to load up here. TypedEnum subclasses get
1239// their own overload (further down) which slices through their Enum
1240// base; the macro-driven generic template must NOT match those or
1241// overload resolution between the two becomes ambiguous.
1242class Enum;
1243
1244namespace Detail {
1245
1248template <typename T>
1249concept IsTypedEnumSubclass = std::is_base_of_v<Enum, T> && !std::is_same_v<T, Enum>;
1250
1251} // namespace Detail
1252
1256template <typename T>
1257 requires Detail::HasPromekiDataType<T> && Detail::HasMemberWriteToStream<T> &&
1258 (!Detail::IsTypedEnumSubclass<T>)
1259inline DataStream &operator<<(DataStream &stream, const T &val) {
1260 stream.beginFrame(T::promekiDataType::id, T::promekiDataType::version);
1261 Error err = val.writeToStream(stream);
1262 stream.endFrame();
1263 if (err.isError() && stream.status() == DataStream::Ok) {
1264 stream.setError(DataStream::WriteFailed,
1265 String("writeToStream returned ") + err.name());
1266 }
1267 return stream;
1268}
1269
1273template <typename T>
1274 requires Detail::HasPromekiDataType<T> && Detail::HasMemberReadFromStream<T> &&
1275 (!Detail::IsTypedEnumSubclass<T>)
1276inline DataStream &operator>>(DataStream &stream, T &val) {
1277 uint16_t ver = 0;
1278 if (!stream.readFrame(T::promekiDataType::id, T::promekiDataType::version, &ver, nullptr)) {
1279 val = T();
1280 return stream;
1281 }
1282 Result<T> r = T::promekiDataType::dispatchRead(stream, ver);
1283 if (r.second().isError()) {
1284 if (stream.status() == DataStream::Ok) {
1285 stream.setError(DataStream::ReadCorruptData,
1286 String("readFromStream returned ") + r.second().name());
1287 }
1288 val = T();
1289 return stream;
1290 }
1291 val = std::move(r.first());
1292 return stream;
1293}
1294
1295// ============================================================================
1296// Slicing overloads for TypedEnum<Self> subclasses
1297//
1298// TypedEnum<X> derives publicly from Enum. When user code does
1299// `stream >> someTypedEnumValue`, the generic operator>> above is
1300// disqualified by the IsTypedEnumSubclass clause; these forward
1301// through the Enum base, exactly mirroring the slicing read the
1302// hand-rolled @c operator>>(Enum&) provided in the legacy design.
1303// ============================================================================
1304
1305template <typename T>
1306 requires Detail::IsTypedEnumSubclass<T>
1307inline DataStream &operator<<(DataStream &stream, const T &val) {
1308 const Enum &base = val;
1309 return stream << base;
1310}
1311
1312template <typename T>
1313 requires Detail::IsTypedEnumSubclass<T>
1314inline DataStream &operator>>(DataStream &stream, T &val) {
1315 Enum &base = val;
1316 return stream >> base;
1317}
1318
1319PROMEKI_NAMESPACE_END
1320
1321#endif // PROMEKI_ENABLE_CORE