diff --git a/internal/base/Archive.go b/internal/base/Archive.go new file mode 100644 index 0000000..112ccc4 --- /dev/null +++ b/internal/base/Archive.go @@ -0,0 +1,333 @@ +package base + +import ( + "encoding/binary" + "fmt" + "io" + "math" + "time" +) + +/*************************************** + * ArchiveBinaryWriter + ***************************************/ + +const ( + BOOL_SIZE int32 = 1 + BYTE_SIZE int32 = 1 + INT32_SIZE int32 = 4 + UINT32_SIZE int32 = 4 + INT64_SIZE int32 = 8 + UINT64_SIZE int32 = 8 + FLOAT32_SIZE int32 = 4 + FLOAT64_SIZE int32 = 8 +) + +const AR_USE_COMPACT_INDICES = false // saves about 8% on -configure output db, but slower due to consecutive ar.Byte() calls instead of fixed ar.Int() + +type ArchiveBinaryReader struct { + reader io.Reader + indexToString []string + basicArchive +} + +func ArchiveBinaryRead(reader io.Reader, scope func(ar Archive), flags ...ArchiveFlag) (err error) { + return Recover(func() error { + ar := NewArchiveBinaryReader(reader, flags...) + defer ar.Close() + scope(&ar) + return ar.Error() + }) +} + +func NewArchiveBinaryReader(reader io.Reader, flags ...ArchiveFlag) ArchiveBinaryReader { + return ArchiveBinaryReader{ + reader: reader, + indexToString: []string{}, + basicArchive: newBasicArchive(append(flags, AR_LOADING)...), + } +} + +func (ar *ArchiveBinaryReader) Close() (err error) { + if cls, ok := ar.reader.(io.Closer); ok { + err = cls.Close() + } + if er := ar.basicArchive.Close(); er != nil && err == nil { + err = er + } + return +} +func (ar *ArchiveBinaryReader) Reset(reader io.Reader) error { + if rst, ok := ar.reader.(ReadReseter); ok { + rst.Reset(reader) + } else { + ar.reader = reader + } + ar.indexToString = ar.indexToString[:0] + return ar.basicArchive.Reset() +} + +func (ar *ArchiveBinaryReader) Raw(value []byte) { + for off := 0; off != len(value); { + if n, err := ar.reader.Read(value[off:]); err == nil { + off += n + } else { + ar.OnError(err) + break + } + } +} +func (ar *ArchiveBinaryReader) Byte(value *byte) { + raw := ar.bytes[:1] + if _, err := ar.reader.Read(raw); err == nil { + *value = raw[0] + } else { + ar.onError(err) + } +} +func (ar *ArchiveBinaryReader) Bool(value *bool) { + var b byte + ar.Byte(&b) + *value = (b != 0) +} +func (ar *ArchiveBinaryReader) Int32(value *int32) { + if AR_USE_COMPACT_INDICES { + SerializeCompactSigned(ar, value) + } else { + raw := ar.bytes[:INT32_SIZE] + ar.Raw(raw) + *value = int32(binary.LittleEndian.Uint32(raw)) + } +} +func (ar *ArchiveBinaryReader) Int64(value *int64) { + if AR_USE_COMPACT_INDICES { + SerializeCompactSigned(ar, value) + } else { + raw := ar.bytes[:INT64_SIZE] + ar.Raw(raw) + *value = int64(binary.LittleEndian.Uint64(raw)) + } +} +func (ar *ArchiveBinaryReader) UInt32(value *uint32) { + if AR_USE_COMPACT_INDICES { + SerializeCompactUnsigned(ar, value) + } else { + raw := ar.bytes[:UINT32_SIZE] + ar.Raw(raw) + *value = binary.LittleEndian.Uint32(raw) + } +} +func (ar *ArchiveBinaryReader) UInt64(value *uint64) { + if AR_USE_COMPACT_INDICES { + SerializeCompactUnsigned(ar, value) + } else { + raw := ar.bytes[:UINT64_SIZE] + ar.Raw(raw) + *value = binary.LittleEndian.Uint64(raw) + } +} +func (ar *ArchiveBinaryReader) Float32(value *float32) { + raw := ar.bytes[:FLOAT32_SIZE] + ar.Raw(raw) + *value = math.Float32frombits(binary.LittleEndian.Uint32(raw)) +} +func (ar *ArchiveBinaryReader) Float64(value *float64) { + raw := ar.bytes[:FLOAT64_SIZE] + ar.Raw(raw) + *value = math.Float64frombits(binary.LittleEndian.Uint64(raw)) +} +func (ar *ArchiveBinaryReader) String(value *string) { + var size int32 + ar.Int32(&size) + + if size < 0 { // check if the length if negative + *value = ar.indexToString[-size-1] // cache hit on string already read + return + } + + AssertErr(func() error { + if size < 2048 { + return nil + } + return fmt.Errorf("serializable: sanity check failed on string length (%d > 2048)", size) + }) + ar.Raw(ar.bytes[:size]) + *value = string(ar.bytes[:size]) + + // record the string for future occurrences + ar.indexToString = append(ar.indexToString, *value) +} +func (ar *ArchiveBinaryReader) Time(value *time.Time) { + raw := ar.bytes[:INT64_SIZE] + ar.Raw(raw) + *value = time.UnixMilli(int64(binary.LittleEndian.Uint64(raw))) +} +func (ar *ArchiveBinaryReader) Serializable(value Serializable) { + value.Serialize(ar) +} + +/*************************************** + * ArchiveBinaryWriter + ***************************************/ + +type ArchiveBinaryWriter struct { + writer io.Writer + str io.StringWriter + stringToIndex map[string]int32 + hasStringWriter bool + basicArchive +} + +func ArchiveBinaryWrite(writer io.Writer, scope func(ar Archive)) (err error) { + return Recover(func() error { + ar := NewArchiveBinaryWriter(writer) + defer ar.Close() + scope(&ar) + return ar.Error() + }) +} + +func NewArchiveBinaryWriter(writer io.Writer, flags ...ArchiveFlag) ArchiveBinaryWriter { + str, hasStringWriter := writer.(io.StringWriter) + return ArchiveBinaryWriter{ + writer: writer, + str: str, + hasStringWriter: hasStringWriter, + stringToIndex: make(map[string]int32), + basicArchive: newBasicArchive(flags...), + } +} + +func (ar *ArchiveBinaryWriter) Close() (err error) { + if cls, ok := ar.writer.(io.Closer); ok { + err = cls.Close() + } + if er := ar.basicArchive.Close(); er != nil && err == nil { + err = er + } + return +} +func (ar *ArchiveBinaryWriter) Reset(writer io.Writer) error { + FlushWriterIFP(writer) + + if rst, ok := ar.writer.(WriteReseter); ok { + rst.Reset(writer) + } else { + ar.writer = writer + ar.str, ar.hasStringWriter = writer.(io.StringWriter) + } + + ar.stringToIndex = make(map[string]int32) + return ar.basicArchive.Reset() +} + +func (ar *ArchiveBinaryWriter) Raw(value []byte) { + for off := 0; off != len(value); { + if n, err := ar.writer.Write(value[off:]); err == nil { + off += n + } else { + ar.OnError(err) + break + } + } +} +func (ar *ArchiveBinaryWriter) Byte(value *byte) { + raw := ar.bytes[:BYTE_SIZE] + raw[0] = *value + if _, err := ar.writer.Write(raw); err != nil { + ar.onError(err) + } +} +func (ar *ArchiveBinaryWriter) Bool(value *bool) { + raw := ar.bytes[:BOOL_SIZE] + raw[0] = 0 + if *value { + raw[0] = 0xFF + } + ar.Raw(raw) +} +func (ar *ArchiveBinaryWriter) Int32(value *int32) { + if AR_USE_COMPACT_INDICES { + SerializeCompactSigned(ar, value) + } else { + raw := ar.bytes[:INT32_SIZE] + binary.LittleEndian.PutUint32(raw, uint32(*value)) + ar.Raw(raw) + } +} +func (ar *ArchiveBinaryWriter) Int64(value *int64) { + if AR_USE_COMPACT_INDICES { + SerializeCompactSigned(ar, value) + } else { + raw := ar.bytes[:INT64_SIZE] + binary.LittleEndian.PutUint64(raw, uint64(*value)) + ar.Raw(raw) + } +} +func (ar *ArchiveBinaryWriter) UInt32(value *uint32) { + if AR_USE_COMPACT_INDICES { + SerializeCompactUnsigned(ar, value) + } else { + raw := ar.bytes[:UINT32_SIZE] + binary.LittleEndian.PutUint32(raw, *value) + ar.Raw(raw) + } +} +func (ar *ArchiveBinaryWriter) UInt64(value *uint64) { + if AR_USE_COMPACT_INDICES { + SerializeCompactUnsigned(ar, value) + } else { + raw := ar.bytes[:UINT64_SIZE] + binary.LittleEndian.PutUint64(raw, *value) + ar.Raw(raw) + } +} +func (ar *ArchiveBinaryWriter) Float32(value *float32) { + raw := ar.bytes[:FLOAT32_SIZE] + binary.LittleEndian.PutUint32(raw, math.Float32bits(*value)) + ar.Raw(raw) +} +func (ar *ArchiveBinaryWriter) Float64(value *float64) { + raw := ar.bytes[:FLOAT64_SIZE] + binary.LittleEndian.PutUint64(raw, math.Float64bits(*value)) + ar.Raw(raw) +} +func (ar *ArchiveBinaryWriter) String(value *string) { + if index, alreadySerialized := ar.stringToIndex[*value]; alreadySerialized { + Assert(func() bool { return index < 0 }) + ar.Int32(&index) // serialize the index, which is negative, instead of the string + return + } else { // record the index in local cache for future occurences + ar.stringToIndex[*value] = int32(-len(ar.stringToIndex) - 1) + } + + // serialize string length, followed by content bytes + + size := int32(len(*value)) // return the number of bytes in string, not number of runes + AssertErr(func() error { + if size < 2048 { + return nil + } + return fmt.Errorf("serializable: sanity check failed on string length (%d > 2048)", size) + }) + ar.Int32(&size) + + if ar.hasStringWriter { // avoid temporary string copy + if n, err := ar.str.WriteString(*value); err != nil { + ar.OnError(err) + } else if int32(n) != size { + ar.OnErrorf("serializable: not enough bytes written -> %d != %d", size, n) + } + + } else { + ar.Raw(UnsafeBytesFromString(*value)) + } +} +func (ar *ArchiveBinaryWriter) Time(value *time.Time) { + raw := ar.bytes[:INT64_SIZE] + binary.LittleEndian.PutUint64(raw, uint64(value.UnixMilli())) + ar.Raw(raw) +} +func (ar *ArchiveBinaryWriter) Serializable(value Serializable) { + value.Serialize(ar) +} diff --git a/internal/base/ArchiveDiff.go b/internal/base/ArchiveDiff.go new file mode 100644 index 0000000..79c265b --- /dev/null +++ b/internal/base/ArchiveDiff.go @@ -0,0 +1,177 @@ +package base + +import ( + "bytes" + "fmt" + "strings" + "time" +) + +/*************************************** + * ArchiveDiff + ***************************************/ + +type ArchiveDiff struct { + buffer *bytes.Buffer + compare ArchiveBinaryReader + stack []Serializable + level int + len int + verbose bool + basicArchive + + OnSerializableBegin PublicEvent[Serializable] + OnSerializableEnd PublicEvent[Serializable] +} + +func SerializableDiff(a, b Serializable) error { + ar := NewArchiveDiff() + defer ar.Close() + return ar.Diff(a, b) +} + +func NewArchiveDiff() ArchiveDiff { + return ArchiveDiff{ + basicArchive: newBasicArchive(), + buffer: TransientBuffer.Allocate(), + verbose: IsLogLevelActive(LOG_TRACE), + } +} +func (x *ArchiveDiff) Close() error { + TransientBuffer.Release(x.buffer) + x.buffer = nil + return nil +} + +func (x *ArchiveDiff) Len() int { + return x.len +} +func (x *ArchiveDiff) Tell() int { + return x.len - x.buffer.Len() +} + +func (x *ArchiveDiff) Diff(a, b Serializable) error { + x.buffer.Reset() + x.err = nil + x.level = 0 + x.stack = []Serializable{} + + return Recover(func() error { + // write a in memory + if ram := NewArchiveBinaryWriter(x.buffer, AR_DETERMINISM); true { + ram.Serializable(a) + if err := ram.Close(); err != nil { + return err + } + } + + // record serialized size + x.len = x.buffer.Len() + + // read b from memory, but do not actually update b + x.compare = NewArchiveBinaryReader(x.buffer, AR_DETERMINISM) + x.compare.flags.Remove(AR_LOADING) // don't want to overwrite b, so we tweak AR_LOADING flag + defer x.compare.Close() + + x.Serializable(b) + return x.Error() + }) +} + +func (x *ArchiveDiff) Flags() ArchiveFlags { + return x.compare.flags +} +func (x *ArchiveDiff) Error() error { + if err := x.basicArchive.Error(); err != nil { + return x.basicArchive.err + } + if err := x.compare.Error(); err != nil { + return err + } + return nil +} + +func (ar ArchiveDiff) serializeLog(format string, args ...interface{}) { + if ar.verbose && IsLogLevelActive(LOG_DEBUG) { + indent := strings.Repeat(" ", ar.level) + LogDebug(LogSerialize, "%s%s", indent, fmt.Sprintf(format, args...)) + } +} +func (x *ArchiveDiff) onDiff(old, new any) { + details := strings.Builder{} + indent := " " + for i, it := range x.stack { + fmt.Fprintf(&details, "%s[%d] %T: %q\n", indent, i, it, it) + indent += " " + } + x.OnErrorf("serializable: difference found -> %q != %q\n%s", old, new, details.String()) +} + +func checkArchiveDiffForScalar[T comparable](ar *ArchiveDiff, serialize func(*T), value *T) { + ar.compare.flags.Add(AR_LOADING) // need to restore AR_LOADING while reading scalar values, + defer ar.compare.flags.Remove(AR_LOADING) // for this scope only + + cmp := *value + serialize(&cmp) + ar.serializeLog("%T: '%v' == '%v'", *value, *value, cmp) + + if cmp != *value { + ar.onDiff(*value, cmp) + } +} + +func (x *ArchiveDiff) Raw(value []byte) { + cmp := x.compare.bytes[:len(value)] + x.compare.Raw(cmp) + x.serializeLog("%T: '%v' == '%v'", value, value, cmp) + if !bytes.Equal(cmp, value) { + x.onDiff(value, cmp) + } +} +func (x *ArchiveDiff) Byte(value *byte) { + checkArchiveDiffForScalar(x, x.compare.Byte, value) +} +func (x *ArchiveDiff) Bool(value *bool) { + checkArchiveDiffForScalar(x, x.compare.Bool, value) +} +func (x *ArchiveDiff) Int32(value *int32) { + checkArchiveDiffForScalar(x, x.compare.Int32, value) +} +func (x *ArchiveDiff) Int64(value *int64) { + checkArchiveDiffForScalar(x, x.compare.Int64, value) +} +func (x *ArchiveDiff) UInt32(value *uint32) { + checkArchiveDiffForScalar(x, x.compare.UInt32, value) +} +func (x *ArchiveDiff) UInt64(value *uint64) { + checkArchiveDiffForScalar(x, x.compare.UInt64, value) +} +func (x *ArchiveDiff) Float32(value *float32) { + checkArchiveDiffForScalar(x, x.compare.Float32, value) +} +func (x *ArchiveDiff) Float64(value *float64) { + checkArchiveDiffForScalar(x, x.compare.Float64, value) +} +func (x *ArchiveDiff) String(value *string) { + checkArchiveDiffForScalar(x, x.compare.String, value) +} +func (x *ArchiveDiff) Time(value *time.Time) { + checkArchiveDiffForScalar(x, x.compare.Time, value) +} +func (x *ArchiveDiff) Serializable(value Serializable) { + x.serializeLog(" --> %T", value) + + x.level++ + x.stack = append(x.stack, value) + + x.OnSerializableBegin.Invoke(value) + + // don't use compare here, or recursive descent won't use diffs functions above + value.Serialize(x) + + x.OnSerializableEnd.Invoke(value) + + AssertIn(x.stack[len(x.stack)-1], value) + x.stack = x.stack[:len(x.stack)-1] + x.level-- +} diff --git a/internal/base/ArchiveFile.go b/internal/base/ArchiveFile.go new file mode 100644 index 0000000..a7a3822 --- /dev/null +++ b/internal/base/ArchiveFile.go @@ -0,0 +1,81 @@ +package base + +import "io" + +/*************************************** + * ArchiveFile + ***************************************/ + +type ArchiveFile struct { + Magic FourCC + Version FourCC + Tags []FourCC +} + +var ArchiveTags = []FourCC{} + +func MakeArchiveTag(tag FourCC) FourCC { + AssertNotIn(tag, ArchiveTags...) + ArchiveTags = append(ArchiveTags, tag) + return tag +} +func MakeArchiveTagIf(tag FourCC, enabled bool) (emptyTag FourCC) { + if enabled { + return MakeArchiveTag(tag) + } + return emptyTag +} + +func NewArchiveFile() ArchiveFile { + return ArchiveFile{ + Magic: MakeFourCC('A', 'R', 'B', 'F'), + Version: MakeFourCC('1', '0', '0', '0'), + Tags: ArchiveTags, + } +} +func (x *ArchiveFile) Serialize(ar Archive) { + ar.Serializable(&x.Magic) + ar.Serializable(&x.Version) + SerializeSlice(ar, &x.Tags) + + // forward serialized tags to the archive + ar.SetTags(x.Tags...) +} + +func ArchiveFileRead(reader io.Reader, scope func(ar Archive), flags ...ArchiveFlag) (file ArchiveFile, err error) { + return file, ArchiveBinaryRead(reader, func(ar Archive) { + ar.Serializable(&file) + if err := ar.Error(); err == nil { + defaultFile := NewArchiveFile() + if file.Magic != defaultFile.Magic { + ar.OnErrorf("archive: invalid file magic (%q != %q)", file.Magic, defaultFile.Magic) + } + if file.Version > defaultFile.Version { + ar.OnErrorf("archive: newer file version (%q > %q)", file.Version, defaultFile.Version) + } + if err = ar.Error(); err == nil { + scope(NewArchiveGuard(ar)) + } + } + }, flags...) +} +func ArchiveFileWrite(writer io.Writer, scope func(ar Archive)) (err error) { + return ArchiveBinaryWrite(writer, func(ar Archive) { + file := NewArchiveFile() + ar.Serializable(&file) + if err := ar.Error(); err == nil { + scope(NewArchiveGuard(ar)) + } + }) +} + +/*************************************** + * CompressedArchiveFile + ***************************************/ + +func CompressedArchiveFileRead(reader io.Reader, scope func(ar Archive), compression ...CompressionOptionFunc) (file ArchiveFile, err error) { + return ArchiveFileRead(NewCompressedReader(reader, compression...), scope) +} +func CompressedArchiveFileWrite(writer io.Writer, scope func(ar Archive), compression ...CompressionOptionFunc) (err error) { + return ArchiveFileWrite(NewCompressedWriter(writer, compression...), scope) +} diff --git a/internal/base/ArchiveGuard.go b/internal/base/ArchiveGuard.go new file mode 100644 index 0000000..22c028d --- /dev/null +++ b/internal/base/ArchiveGuard.go @@ -0,0 +1,160 @@ +package base + +import ( + "fmt" + "reflect" + "strings" + "time" +) + +/*************************************** + * ArchiveGuard + ***************************************/ + +const ( + ARCHIVEGUARD_ENABLED = false + ARCHIVEGUARD_CANARY uint32 = 0xDEADBEEF +) + +var ( + ArchiveGuardTag = MakeArchiveTagIf(MakeFourCC('G', 'A', 'R', 'D'), ARCHIVEGUARD_ENABLED) + + ARCHIVEGUARD_TAG_RAW FourCC = MakeFourCC('R', 'A', 'W', 'B') + ARCHIVEGUARD_TAG_BYTE FourCC = MakeFourCC('B', 'Y', 'T', 'E') + ARCHIVEGUARD_TAG_BOOL FourCC = MakeFourCC('B', 'O', 'O', 'L') + ARCHIVEGUARD_TAG_INT32 FourCC = MakeFourCC('S', 'I', '3', '2') + ARCHIVEGUARD_TAG_INT64 FourCC = MakeFourCC('S', 'I', '6', '4') + ARCHIVEGUARD_TAG_UINT32 FourCC = MakeFourCC('U', 'I', '3', '2') + ARCHIVEGUARD_TAG_UINT64 FourCC = MakeFourCC('U', 'I', '6', '4') + ARCHIVEGUARD_TAG_FLOAT32 FourCC = MakeFourCC('F', 'T', '3', '2') + ARCHIVEGUARD_TAG_FLOAT64 FourCC = MakeFourCC('F', 'T', '6', '4') + ARCHIVEGUARD_TAG_STRING FourCC = MakeFourCC('S', 'T', 'R', 'G') + ARCHIVEGUARD_TAG_TIME FourCC = MakeFourCC('T', 'I', 'M', 'E') + ARCHIVEGUARD_TAG_SERIALIZABLE FourCC = MakeFourCC('S', 'R', 'L', 'Z') +) + +type ArchiveGuard struct { + inner Archive + level int +} + +func NewArchiveGuard(ar Archive) Archive { + if ar.HasTags(ArchiveGuardTag) { + return ArchiveGuard{inner: ar} + } else { + return ar + } +} + +func (ar ArchiveGuard) serializeLog(format string, args ...interface{}) { + if IsLogLevelActive(LOG_DEBUG) { + indent := strings.Repeat(" ", ar.level) + LogDebug(LogSerialize, "%s%s", indent, fmt.Sprintf(format, args...)) + } +} +func (ar ArchiveGuard) checkTag(tag FourCC) { + canary := ARCHIVEGUARD_CANARY + if ar.inner.UInt32(&canary); canary != ARCHIVEGUARD_CANARY { + LogPanic(LogSerialize, "invalid canary guard : 0x%08X vs 0x%08X", canary, ARCHIVEGUARD_CANARY) + } + + check := tag + if check.Serialize(ar.inner); tag != check { + LogPanic(LogSerialize, "invalid tag guard : %s (0x%08X) vs %s (0x%08X)", check, check, tag, tag) + } +} +func (ar *ArchiveGuard) typeGuard(value any, tag FourCC, format string, args ...interface{}) func() { + ar.serializeLog(format, args...) + ar.checkTag(tag) + + if tag == ARCHIVEGUARD_TAG_SERIALIZABLE { + ar.level++ + } + + return func() { + LogPanicIfFailed(LogSerialize, ar.inner.Error()) + ar.checkTag(tag) + + vo := reflect.ValueOf(value) + if vo.Kind() == reflect.Pointer { + vo = vo.Elem() + } + ar.serializeLog("\t-> %v: %v", vo.Type(), vo) + + if tag == ARCHIVEGUARD_TAG_SERIALIZABLE { + ar.level-- + } + } +} + +func (ar ArchiveGuard) Factory() SerializableFactory { return ar.inner.Factory() } +func (ar ArchiveGuard) Error() error { return ar.inner.Error() } +func (ar ArchiveGuard) OnError(err error) { ar.inner.OnError(err) } +func (ar ArchiveGuard) OnErrorf(msg string, args ...any) { ar.inner.OnErrorf(msg, args...) } +func (ar ArchiveGuard) HasTags(tags ...FourCC) bool { return ar.inner.HasTags(tags...) } +func (ar ArchiveGuard) SetTags(tags ...FourCC) { ar.inner.SetTags(tags...) } + +func (ar ArchiveGuard) Flags() ArchiveFlags { + return ar.inner.Flags() +} + +func (ar ArchiveGuard) Raw(value []byte) { + Assert(func() bool { return len(value) > 0 }) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_RAW, "ar.Raw(%d)", len(value))() + ar.inner.Raw(value) +} +func (ar ArchiveGuard) Byte(value *byte) { + AssertNotIn(value, nil) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_BYTE, "ar.Byte()")() + ar.inner.Byte(value) +} +func (ar ArchiveGuard) Bool(value *bool) { + AssertNotIn(value, nil) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_BOOL, "ar.Bool()")() + ar.inner.Bool(value) +} +func (ar ArchiveGuard) Int32(value *int32) { + AssertNotIn(value, nil) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_INT32, "ar.Int32()")() + ar.inner.Int32(value) +} +func (ar ArchiveGuard) Int64(value *int64) { + AssertNotIn(value, nil) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_INT64, "ar.Int64()")() + ar.inner.Int64(value) +} +func (ar ArchiveGuard) UInt32(value *uint32) { + AssertNotIn(value, nil) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_UINT32, "ar.UInt32()")() + ar.inner.UInt32(value) +} +func (ar ArchiveGuard) UInt64(value *uint64) { + AssertNotIn(value, nil) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_UINT64, "ar.UInt64()")() + ar.inner.UInt64(value) +} +func (ar ArchiveGuard) Float32(value *float32) { + AssertNotIn(value, nil) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_FLOAT32, "ar.Float32()")() + ar.inner.Float32(value) +} +func (ar ArchiveGuard) Float64(value *float64) { + AssertNotIn(value, nil) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_FLOAT64, "ar.Float64()")() + ar.inner.Float64(value) +} +func (ar ArchiveGuard) String(value *string) { + AssertNotIn(value, nil) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_STRING, "ar.String()")() + ar.inner.String(value) +} +func (ar ArchiveGuard) Time(value *time.Time) { + AssertNotIn(value, nil) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_TIME, "ar.Time()")() + ar.inner.Time(value) +} +func (ar ArchiveGuard) Serializable(value Serializable) { + Assert(func() bool { return !IsNil(value) }) + defer ar.typeGuard(value, ARCHIVEGUARD_TAG_SERIALIZABLE, "ar.Serializable(%T)", value)() + value.Serialize(ar) // don't use inner here, or recursive descent won't use guards +} diff --git a/internal/base/Serializable.go b/internal/base/Serializable.go index e206066..0c9d5ea 100644 --- a/internal/base/Serializable.go +++ b/internal/base/Serializable.go @@ -1,15 +1,10 @@ package base import ( - "bytes" - "encoding/binary" "encoding/hex" "fmt" - "io" - "math" "reflect" "sort" - "strings" "sync" "time" @@ -18,10 +13,8 @@ import ( var LogSerialize = NewLogCategory("Serialize") -const AR_USE_COMPACT_INDICES = false // saves about 8% on -configure output db, but slower due to consecutive ar.Byte() calls instead of fixed ar.Int() - /*************************************** - * Archive + * Archive Flags ***************************************/ type ArchiveFlag int32 @@ -30,7 +23,6 @@ const ( AR_LOADING ArchiveFlag = iota AR_DETERMINISM AR_TOLERANT - AR_GUARD ) func (x ArchiveFlag) Ord() int32 { return int32(x) } @@ -43,8 +35,6 @@ func (x *ArchiveFlag) Set(in string) (err error) { *x = AR_DETERMINISM case AR_TOLERANT.String(): *x = AR_TOLERANT - case AR_GUARD.String(): - *x = AR_GUARD default: err = fmt.Errorf("unkown archive flags: %v", in) } @@ -59,8 +49,6 @@ func (x ArchiveFlag) String() (str string) { str = "DETERMINISM" case AR_TOLERANT: str = "TOLERANT" - case AR_GUARD: - str = "GUARD" default: UnexpectedValuePanic(x, x) } @@ -80,20 +68,10 @@ func (fl ArchiveFlags) IsDeterministic() bool { func (fl ArchiveFlags) IsTolerant() bool { return fl.Has(AR_TOLERANT) } -func (fl ArchiveFlags) IsGuarded() bool { - return fl.Has(AR_GUARD) -} -const ( - BOOL_SIZE int32 = 1 - BYTE_SIZE int32 = 1 - INT32_SIZE int32 = 4 - UINT32_SIZE int32 = 4 - INT64_SIZE int32 = 8 - UINT64_SIZE int32 = 8 - FLOAT32_SIZE int32 = 4 - FLOAT64_SIZE int32 = 8 -) +/*************************************** + * Archive + ***************************************/ type Archive interface { Factory() SerializableFactory @@ -244,6 +222,16 @@ func (x *serializableBatchNew[T]) Allocate() *T { return p } +func reflectSerializable[T Serializable](factory SerializableFactory, value T) serializableGuid { + emptyPtr := getEmptyInterface(value) + AssertNotIn(emptyPtr.typ, nil) + + return factory.ResolveTypename(uintptr(emptyPtr.typ)) +} +func resolveSerializable(factory SerializableFactory, guid serializableGuid) Serializable { + return factory.CreateNew(guid) +} + func RegisterSerializable[T any, S interface { *T Serializable @@ -263,16 +251,6 @@ func RegisterSerializable[T any, S interface { }) } -func reflectSerializable[T Serializable](factory SerializableFactory, value T) serializableGuid { - emptyPtr := getEmptyInterface(value) - AssertNotIn(emptyPtr.typ, nil) - - return factory.ResolveTypename(uintptr(emptyPtr.typ)) -} -func resolveSerializable(factory SerializableFactory, guid serializableGuid) Serializable { - return factory.CreateNew(guid) -} - /*************************************** * Archive Container Helpers ***************************************/ @@ -543,713 +521,3 @@ func (x *basicArchive) Reset() (err error) { x.err = nil return } - -/*************************************** - * ArchiveBinaryWriter - ***************************************/ - -type ArchiveBinaryReader struct { - reader io.Reader - indexToString []string - basicArchive -} - -func ArchiveBinaryRead(reader io.Reader, scope func(ar Archive)) (err error) { - return Recover(func() error { - ar := NewArchiveBinaryReader(reader) - defer ar.Close() - scope(NewArchiveGuard(&ar)) - return ar.Error() - }) -} - -func NewArchiveBinaryReader(reader io.Reader, flags ...ArchiveFlag) ArchiveBinaryReader { - return ArchiveBinaryReader{ - reader: reader, - indexToString: []string{}, - basicArchive: newBasicArchive(append(flags, AR_LOADING)...), - } -} - -func (ar *ArchiveBinaryReader) Close() (err error) { - if cls, ok := ar.reader.(io.Closer); ok { - err = cls.Close() - } - if er := ar.basicArchive.Close(); er != nil && err == nil { - err = er - } - return -} -func (ar *ArchiveBinaryReader) Reset(reader io.Reader) error { - if rst, ok := ar.reader.(ReadReseter); ok { - rst.Reset(reader) - } else { - ar.reader = reader - } - ar.indexToString = ar.indexToString[:0] - return ar.basicArchive.Reset() -} - -func (ar *ArchiveBinaryReader) Raw(value []byte) { - for off := 0; off != len(value); { - if n, err := ar.reader.Read(value[off:]); err == nil { - off += n - } else { - ar.OnError(err) - break - } - } -} -func (ar *ArchiveBinaryReader) Byte(value *byte) { - raw := ar.bytes[:1] - if _, err := ar.reader.Read(raw); err == nil { - *value = raw[0] - } else { - ar.onError(err) - } -} -func (ar *ArchiveBinaryReader) Bool(value *bool) { - var b byte - ar.Byte(&b) - *value = (b != 0) -} -func (ar *ArchiveBinaryReader) Int32(value *int32) { - if AR_USE_COMPACT_INDICES { - SerializeCompactSigned(ar, value) - } else { - raw := ar.bytes[:INT32_SIZE] - ar.Raw(raw) - *value = int32(binary.LittleEndian.Uint32(raw)) - } -} -func (ar *ArchiveBinaryReader) Int64(value *int64) { - if AR_USE_COMPACT_INDICES { - SerializeCompactSigned(ar, value) - } else { - raw := ar.bytes[:INT64_SIZE] - ar.Raw(raw) - *value = int64(binary.LittleEndian.Uint64(raw)) - } -} -func (ar *ArchiveBinaryReader) UInt32(value *uint32) { - if AR_USE_COMPACT_INDICES { - SerializeCompactUnsigned(ar, value) - } else { - raw := ar.bytes[:UINT32_SIZE] - ar.Raw(raw) - *value = binary.LittleEndian.Uint32(raw) - } -} -func (ar *ArchiveBinaryReader) UInt64(value *uint64) { - if AR_USE_COMPACT_INDICES { - SerializeCompactUnsigned(ar, value) - } else { - raw := ar.bytes[:UINT64_SIZE] - ar.Raw(raw) - *value = binary.LittleEndian.Uint64(raw) - } -} -func (ar *ArchiveBinaryReader) Float32(value *float32) { - raw := ar.bytes[:FLOAT32_SIZE] - ar.Raw(raw) - *value = math.Float32frombits(binary.LittleEndian.Uint32(raw)) -} -func (ar *ArchiveBinaryReader) Float64(value *float64) { - raw := ar.bytes[:FLOAT64_SIZE] - ar.Raw(raw) - *value = math.Float64frombits(binary.LittleEndian.Uint64(raw)) -} -func (ar *ArchiveBinaryReader) String(value *string) { - var size int32 - ar.Int32(&size) - - if size < 0 { // check if the length if negative - *value = ar.indexToString[-size-1] // cache hit on string already read - return - } - - AssertErr(func() error { - if size < 2048 { - return nil - } - return fmt.Errorf("serializable: sanity check failed on string length (%d > 2048)", size) - }) - ar.Raw(ar.bytes[:size]) - *value = string(ar.bytes[:size]) - - // record the string for future occurrences - ar.indexToString = append(ar.indexToString, *value) -} -func (ar *ArchiveBinaryReader) Time(value *time.Time) { - raw := ar.bytes[:INT64_SIZE] - ar.Raw(raw) - *value = time.UnixMilli(int64(binary.LittleEndian.Uint64(raw))) -} -func (ar *ArchiveBinaryReader) Serializable(value Serializable) { - value.Serialize(ar) -} - -/*************************************** - * ArchiveBinaryWriter - ***************************************/ - -type ArchiveBinaryWriter struct { - writer io.Writer - str io.StringWriter - hasStringWriter bool - stringToIndex map[string]int32 - basicArchive -} - -func ArchiveBinaryWrite(writer io.Writer, scope func(ar Archive)) (err error) { - return Recover(func() error { - ar := NewArchiveBinaryWriter(writer) - defer ar.Close() - scope(NewArchiveGuard(&ar)) - return ar.Error() - }) -} - -func NewArchiveBinaryWriter(writer io.Writer, flags ...ArchiveFlag) ArchiveBinaryWriter { - str, hasStringWriter := writer.(io.StringWriter) - return ArchiveBinaryWriter{ - writer: writer, - str: str, - hasStringWriter: hasStringWriter, - stringToIndex: make(map[string]int32), - basicArchive: newBasicArchive(flags...), - } -} - -func (ar *ArchiveBinaryWriter) Close() (err error) { - if cls, ok := ar.writer.(io.Closer); ok { - err = cls.Close() - } - if er := ar.basicArchive.Close(); er != nil && err == nil { - err = er - } - return -} -func (ar *ArchiveBinaryWriter) Reset(writer io.Writer) error { - FlushWriterIFP(writer) - - if rst, ok := ar.writer.(WriteReseter); ok { - rst.Reset(writer) - } else { - ar.writer = writer - ar.str, ar.hasStringWriter = writer.(io.StringWriter) - } - - ar.stringToIndex = make(map[string]int32) - return ar.basicArchive.Reset() -} - -func (ar *ArchiveBinaryWriter) Raw(value []byte) { - for off := 0; off != len(value); { - if n, err := ar.writer.Write(value[off:]); err == nil { - off += n - } else { - ar.OnError(err) - break - } - } -} -func (ar *ArchiveBinaryWriter) Byte(value *byte) { - raw := ar.bytes[:BYTE_SIZE] - raw[0] = *value - if _, err := ar.writer.Write(raw); err != nil { - ar.onError(err) - } -} -func (ar *ArchiveBinaryWriter) Bool(value *bool) { - raw := ar.bytes[:BOOL_SIZE] - raw[0] = 0 - if *value { - raw[0] = 0xFF - } - ar.Raw(raw) -} -func (ar *ArchiveBinaryWriter) Int32(value *int32) { - if AR_USE_COMPACT_INDICES { - SerializeCompactSigned(ar, value) - } else { - raw := ar.bytes[:INT32_SIZE] - binary.LittleEndian.PutUint32(raw, uint32(*value)) - ar.Raw(raw) - } -} -func (ar *ArchiveBinaryWriter) Int64(value *int64) { - if AR_USE_COMPACT_INDICES { - SerializeCompactSigned(ar, value) - } else { - raw := ar.bytes[:INT64_SIZE] - binary.LittleEndian.PutUint64(raw, uint64(*value)) - ar.Raw(raw) - } -} -func (ar *ArchiveBinaryWriter) UInt32(value *uint32) { - if AR_USE_COMPACT_INDICES { - SerializeCompactUnsigned(ar, value) - } else { - raw := ar.bytes[:UINT32_SIZE] - binary.LittleEndian.PutUint32(raw, *value) - ar.Raw(raw) - } -} -func (ar *ArchiveBinaryWriter) UInt64(value *uint64) { - if AR_USE_COMPACT_INDICES { - SerializeCompactUnsigned(ar, value) - } else { - raw := ar.bytes[:UINT64_SIZE] - binary.LittleEndian.PutUint64(raw, *value) - ar.Raw(raw) - } -} -func (ar *ArchiveBinaryWriter) Float32(value *float32) { - raw := ar.bytes[:FLOAT32_SIZE] - binary.LittleEndian.PutUint32(raw, math.Float32bits(*value)) - ar.Raw(raw) -} -func (ar *ArchiveBinaryWriter) Float64(value *float64) { - raw := ar.bytes[:FLOAT64_SIZE] - binary.LittleEndian.PutUint64(raw, math.Float64bits(*value)) - ar.Raw(raw) -} -func (ar *ArchiveBinaryWriter) String(value *string) { - if index, alreadySerialized := ar.stringToIndex[*value]; alreadySerialized { - Assert(func() bool { return index < 0 }) - ar.Int32(&index) // serialize the index, which is negative, instead of the string - return - } else { // record the index in local cache for future occurences - ar.stringToIndex[*value] = int32(-len(ar.stringToIndex) - 1) - } - - // serialize string length, followed by content bytes - - size := int32(len(*value)) // return the number of bytes in string, not number of runes - AssertErr(func() error { - if size < 2048 { - return nil - } - return fmt.Errorf("serializable: sanity check failed on string length (%d > 2048)", size) - }) - ar.Int32(&size) - - if ar.hasStringWriter { // avoid temporary string copy - if n, err := ar.str.WriteString(*value); err != nil { - ar.OnError(err) - } else if int32(n) != size { - ar.OnErrorf("serializable: not enough bytes written -> %d != %d", size, n) - } - - } else { - ar.Raw(UnsafeBytesFromString(*value)) - } -} -func (ar *ArchiveBinaryWriter) Time(value *time.Time) { - raw := ar.bytes[:INT64_SIZE] - binary.LittleEndian.PutUint64(raw, uint64(value.UnixMilli())) - ar.Raw(raw) -} -func (ar *ArchiveBinaryWriter) Serializable(value Serializable) { - value.Serialize(ar) -} - -/*************************************** - * ArchiveFile - ***************************************/ - -type ArchiveFile struct { - Magic FourCC - Version FourCC - Tags []FourCC -} - -var ArchiveTags = []FourCC{} - -func MakeArchiveTag(tag FourCC) FourCC { - AssertNotIn(tag, ArchiveTags...) - ArchiveTags = append(ArchiveTags, tag) - return tag -} - -func NewArchiveFile() ArchiveFile { - return ArchiveFile{ - Magic: MakeFourCC('A', 'R', 'B', 'F'), - Version: MakeFourCC('1', '0', '0', '0'), - Tags: ArchiveTags, - } -} -func (x *ArchiveFile) Serialize(ar Archive) { - ar.Serializable(&x.Magic) - ar.Serializable(&x.Version) - SerializeSlice(ar, &x.Tags) - - // forward serialized tags to the archive - ar.SetTags(x.Tags...) -} - -func ArchiveFileRead(reader io.Reader, scope func(ar Archive)) (file ArchiveFile, err error) { - return file, ArchiveBinaryRead(reader, func(ar Archive) { - ar.Serializable(&file) - if err := ar.Error(); err == nil { - defaultFile := NewArchiveFile() - if file.Magic != defaultFile.Magic { - ar.OnErrorf("archive: invalid file magic (%q != %q)", file.Magic, defaultFile.Magic) - } - if file.Version > defaultFile.Version { - ar.OnErrorf("archive: newer file version (%q > %q)", file.Version, defaultFile.Version) - } - if err = ar.Error(); err == nil { - scope(ar) - } - } - }) -} -func ArchiveFileWrite(writer io.Writer, scope func(ar Archive)) (err error) { - return ArchiveBinaryWrite(writer, func(ar Archive) { - file := NewArchiveFile() - ar.Serializable(&file) - if err := ar.Error(); err == nil { - scope(ar) - } - }) -} - -/*************************************** - * CompressedArchiveFile - ***************************************/ - -func CompressedArchiveFileRead(reader io.Reader, scope func(ar Archive), compression ...CompressionOptionFunc) (file ArchiveFile, err error) { - return ArchiveFileRead(NewCompressedReader(reader, compression...), scope) -} -func CompressedArchiveFileWrite(writer io.Writer, scope func(ar Archive), compression ...CompressionOptionFunc) (err error) { - return ArchiveFileWrite(NewCompressedWriter(writer, compression...), scope) -} - -/*************************************** - * ArchiveDiff - ***************************************/ - -type ArchiveDiff struct { - buffer *bytes.Buffer - compare ArchiveBinaryReader - stack []Serializable - level int - len int - verbose bool - basicArchive - - OnSerializableBegin PublicEvent[Serializable] - OnSerializableEnd PublicEvent[Serializable] -} - -func SerializableDiff(a, b Serializable) error { - ar := NewArchiveDiff() - defer ar.Close() - return ar.Diff(a, b) -} - -func NewArchiveDiff() ArchiveDiff { - return ArchiveDiff{ - basicArchive: newBasicArchive(), - buffer: TransientBuffer.Allocate(), - verbose: IsLogLevelActive(LOG_TRACE), - } -} -func (x *ArchiveDiff) Close() error { - TransientBuffer.Release(x.buffer) - x.buffer = nil - return nil -} - -func (x *ArchiveDiff) Len() int { - return x.len -} -func (x *ArchiveDiff) Tell() int { - return x.len - x.buffer.Len() -} - -func (x *ArchiveDiff) Diff(a, b Serializable) error { - x.buffer.Reset() - x.err = nil - x.level = 0 - x.stack = []Serializable{} - - return Recover(func() error { - // write a in memory - if ram := NewArchiveBinaryWriter(x.buffer, AR_DETERMINISM); true { - ram.Serializable(a) - if err := ram.Close(); err != nil { - return err - } - } - - // record serialized size - x.len = x.buffer.Len() - - // read b from memory, but do not actually update b - x.compare = NewArchiveBinaryReader(x.buffer, AR_DETERMINISM) - x.compare.flags.Remove(AR_LOADING) // don't want to overwrite b, so we tweak AR_LOADING flag - defer x.compare.Close() - - x.Serializable(b) - return x.Error() - }) -} - -func (x *ArchiveDiff) Flags() ArchiveFlags { - return x.compare.flags -} -func (x *ArchiveDiff) Error() error { - if err := x.basicArchive.Error(); err != nil { - return x.basicArchive.err - } - if err := x.compare.Error(); err != nil { - return err - } - return nil -} - -func (ar ArchiveDiff) serializeLog(format string, args ...interface{}) { - if ar.verbose && IsLogLevelActive(LOG_DEBUG) { - indent := strings.Repeat(" ", ar.level) - LogDebug(LogSerialize, "%s%s", indent, fmt.Sprintf(format, args...)) - } -} -func (x *ArchiveDiff) onDiff(old, new any) { - details := strings.Builder{} - indent := " " - for i, it := range x.stack { - fmt.Fprintf(&details, "%s[%d] %T: %q\n", indent, i, it, it) - indent += " " - } - x.OnErrorf("serializable: difference found -> %q != %q\n%s", old, new, details.String()) -} - -func checkArchiveDiffForScalar[T comparable](ar *ArchiveDiff, serialize func(*T), value *T) { - ar.compare.flags.Add(AR_LOADING) // need to restore AR_LOADING while reading scalar values, - defer ar.compare.flags.Remove(AR_LOADING) // for this scope only - - cmp := *value - serialize(&cmp) - ar.serializeLog("%T: '%v' == '%v'", *value, *value, cmp) - - if cmp != *value { - ar.onDiff(*value, cmp) - } -} - -func (x *ArchiveDiff) Raw(value []byte) { - cmp := x.compare.bytes[:len(value)] - x.compare.Raw(cmp) - x.serializeLog("%T: '%v' == '%v'", value, value, cmp) - if !bytes.Equal(cmp, value) { - x.onDiff(value, cmp) - } -} -func (x *ArchiveDiff) Byte(value *byte) { - checkArchiveDiffForScalar(x, x.compare.Byte, value) -} -func (x *ArchiveDiff) Bool(value *bool) { - checkArchiveDiffForScalar(x, x.compare.Bool, value) -} -func (x *ArchiveDiff) Int32(value *int32) { - checkArchiveDiffForScalar(x, x.compare.Int32, value) -} -func (x *ArchiveDiff) Int64(value *int64) { - checkArchiveDiffForScalar(x, x.compare.Int64, value) -} -func (x *ArchiveDiff) UInt32(value *uint32) { - checkArchiveDiffForScalar(x, x.compare.UInt32, value) -} -func (x *ArchiveDiff) UInt64(value *uint64) { - checkArchiveDiffForScalar(x, x.compare.UInt64, value) -} -func (x *ArchiveDiff) Float32(value *float32) { - checkArchiveDiffForScalar(x, x.compare.Float32, value) -} -func (x *ArchiveDiff) Float64(value *float64) { - checkArchiveDiffForScalar(x, x.compare.Float64, value) -} -func (x *ArchiveDiff) String(value *string) { - checkArchiveDiffForScalar(x, x.compare.String, value) -} -func (x *ArchiveDiff) Time(value *time.Time) { - checkArchiveDiffForScalar(x, x.compare.Time, value) -} -func (x *ArchiveDiff) Serializable(value Serializable) { - x.serializeLog(" --> %T", value) - - x.level++ - x.stack = append(x.stack, value) - - x.OnSerializableBegin.Invoke(value) - - // don't use compare here, or recursive descent won't use diffs functions above - value.Serialize(x) - - x.OnSerializableEnd.Invoke(value) - - AssertIn(x.stack[len(x.stack)-1], value) - x.stack = x.stack[:len(x.stack)-1] - x.level-- -} - -/*************************************** - * ArchiveGuard - ***************************************/ - -const ( - ARCHIVEGUARD_ENABLED = false - ARCHIVEGUARD_CANARY uint32 = 0xDEADBEEF -) - -var ( - ARCHIVEGUARD_TAG_RAW FourCC = MakeFourCC('R', 'A', 'W', 'B') - ARCHIVEGUARD_TAG_BYTE FourCC = MakeFourCC('B', 'Y', 'T', 'E') - ARCHIVEGUARD_TAG_BOOL FourCC = MakeFourCC('B', 'O', 'O', 'L') - ARCHIVEGUARD_TAG_INT32 FourCC = MakeFourCC('S', 'I', '3', '2') - ARCHIVEGUARD_TAG_INT64 FourCC = MakeFourCC('S', 'I', '6', '4') - ARCHIVEGUARD_TAG_UINT32 FourCC = MakeFourCC('U', 'I', '3', '2') - ARCHIVEGUARD_TAG_UINT64 FourCC = MakeFourCC('U', 'I', '6', '4') - ARCHIVEGUARD_TAG_FLOAT32 FourCC = MakeFourCC('F', 'T', '3', '2') - ARCHIVEGUARD_TAG_FLOAT64 FourCC = MakeFourCC('F', 'T', '6', '4') - ARCHIVEGUARD_TAG_STRING FourCC = MakeFourCC('S', 'T', 'R', 'G') - ARCHIVEGUARD_TAG_TIME FourCC = MakeFourCC('T', 'I', 'M', 'E') - ARCHIVEGUARD_TAG_SERIALIZABLE FourCC = MakeFourCC('S', 'R', 'L', 'Z') -) - -type ArchiveGuard struct { - inner Archive - level int -} - -func NewArchiveGuard(ar Archive) Archive { - shouldUseGuard := false - if ar.Flags().IsLoading() { - shouldUseGuard = ar.Flags().IsGuarded() - } else { - shouldUseGuard = ARCHIVEGUARD_ENABLED - } - if shouldUseGuard { - return ArchiveGuard{inner: ar} - } else { - return ar - } -} - -func (ar ArchiveGuard) serializeLog(format string, args ...interface{}) { - if IsLogLevelActive(LOG_DEBUG) { - indent := strings.Repeat(" ", ar.level) - LogDebug(LogSerialize, "%s%s", indent, fmt.Sprintf(format, args...)) - } -} -func (ar ArchiveGuard) checkTag(tag FourCC) { - canary := ARCHIVEGUARD_CANARY - if ar.inner.UInt32(&canary); canary != ARCHIVEGUARD_CANARY { - LogPanic(LogSerialize, "invalid canary guard : 0x%08X vs 0x%08X", canary, ARCHIVEGUARD_CANARY) - } - - check := tag - if check.Serialize(ar.inner); tag != check { - LogPanic(LogSerialize, "invalid tag guard : %s (0x%08X) vs %s (0x%08X)", check, check, tag, tag) - } -} -func (ar *ArchiveGuard) typeGuard(value any, tag FourCC, format string, args ...interface{}) func() { - ar.serializeLog(format, args...) - ar.checkTag(tag) - - if tag == ARCHIVEGUARD_TAG_SERIALIZABLE { - ar.level++ - } - - return func() { - LogPanicIfFailed(LogSerialize, ar.inner.Error()) - ar.checkTag(tag) - - vo := reflect.ValueOf(value) - if vo.Kind() == reflect.Pointer { - vo = vo.Elem() - } - ar.serializeLog("\t-> %v: %v", vo.Type(), vo) - - if tag == ARCHIVEGUARD_TAG_SERIALIZABLE { - ar.level-- - } - } -} - -func (ar ArchiveGuard) Factory() SerializableFactory { return ar.inner.Factory() } -func (ar ArchiveGuard) Error() error { return ar.inner.Error() } -func (ar ArchiveGuard) OnError(err error) { ar.inner.OnError(err) } -func (ar ArchiveGuard) OnErrorf(msg string, args ...any) { ar.inner.OnErrorf(msg, args...) } -func (ar ArchiveGuard) HasTags(tags ...FourCC) bool { return ar.inner.HasTags(tags...) } -func (ar ArchiveGuard) SetTags(tags ...FourCC) { ar.inner.SetTags(tags...) } - -func (ar ArchiveGuard) Flags() ArchiveFlags { - flags := ar.inner.Flags() - flags.Add(AR_GUARD) - return flags -} - -func (ar ArchiveGuard) Raw(value []byte) { - Assert(func() bool { return len(value) > 0 }) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_RAW, "ar.Raw(%d)", len(value))() - ar.inner.Raw(value) -} -func (ar ArchiveGuard) Byte(value *byte) { - AssertNotIn(value, nil) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_BYTE, "ar.Byte()")() - ar.inner.Byte(value) -} -func (ar ArchiveGuard) Bool(value *bool) { - AssertNotIn(value, nil) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_BOOL, "ar.Bool()")() - ar.inner.Bool(value) -} -func (ar ArchiveGuard) Int32(value *int32) { - AssertNotIn(value, nil) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_INT32, "ar.Int32()")() - ar.inner.Int32(value) -} -func (ar ArchiveGuard) Int64(value *int64) { - AssertNotIn(value, nil) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_INT64, "ar.Int64()")() - ar.inner.Int64(value) -} -func (ar ArchiveGuard) UInt32(value *uint32) { - AssertNotIn(value, nil) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_UINT32, "ar.UInt32()")() - ar.inner.UInt32(value) -} -func (ar ArchiveGuard) UInt64(value *uint64) { - AssertNotIn(value, nil) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_UINT64, "ar.UInt64()")() - ar.inner.UInt64(value) -} -func (ar ArchiveGuard) Float32(value *float32) { - AssertNotIn(value, nil) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_FLOAT32, "ar.Float32()")() - ar.inner.Float32(value) -} -func (ar ArchiveGuard) Float64(value *float64) { - AssertNotIn(value, nil) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_FLOAT64, "ar.Float64()")() - ar.inner.Float64(value) -} -func (ar ArchiveGuard) String(value *string) { - AssertNotIn(value, nil) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_STRING, "ar.String()")() - ar.inner.String(value) -} -func (ar ArchiveGuard) Time(value *time.Time) { - AssertNotIn(value, nil) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_TIME, "ar.Time()")() - ar.inner.Time(value) -} -func (ar ArchiveGuard) Serializable(value Serializable) { - Assert(func() bool { return !IsNil(value) }) - defer ar.typeGuard(value, ARCHIVEGUARD_TAG_SERIALIZABLE, "ar.Serializable(%T)", value)() - value.Serialize(ar) // don't use inner here, or recursive descent won't use guards -}