Skip to content

Instantly share code, notes, and snippets.

@niwinz
Last active September 26, 2025 08:54
Show Gist options
  • Save niwinz/6022b2a574f44bdce817c38ac75fd9f7 to your computer and use it in GitHub Desktop.
Save niwinz/6022b2a574f44bdce817c38ac75fd9f7 to your computer and use it in GitHub Desktop.
Neested serialization strategy based map (TransitMap, ObjectsMap)
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.types.objects-map2
"Implements a specialized map-like data structure for store an UUID =>
OBJECT mappings. The main purpose of this data structure is be able
to serialize it on fressian as byte-array and have the ability to
decode each field separatelly without the need to decode the whole
map from the byte-array.
It works transparently, so no aditional dynamic vars are needed. It
only works by reference equality and the hash-code is calculated
properly from each value."
(:require
#?(:clj [clojure.data.json :as json])
#?(:clj [app.common.fressian :as fres])
[clojure.core.protocols :as cp]
[app.common.data :as d]
[app.common.transit :as t]
[app.common.uuid :as uuid]
[app.common.buffer :as buf]
[clojure.core :as c])
#?(:clj
(:import
clojure.lang.Counted
clojure.lang.IHashEq
clojure.lang.IMapEntry
clojure.lang.IObj
clojure.lang.IPersistentCollection
clojure.lang.IPersistentMap
clojure.lang.Murmur3
clojure.lang.RT
clojure.lang.Seqable
java.nio.ByteBuffer
java.util.Iterator
java.util.UUID)))
#?(:clj (set! *warn-on-reflection* true))
(def ^:dynamic *lazy* true)
(def RECORD-SIZE (+ 16 8))
(declare create)
(declare ^:private initialize)
(declare ^:private read-index)
(declare ^:private read-objects)
(declare ^:private do-compact)
(defprotocol IObjectsMap
(-initialize [_] "read the header and initialize the the object map instance")
(-compact [_])
(-hash-for-key [_ key] "retrieve a hash for a key")
(-get-buffer [_] "retrieve internal buffer"))
#?(:clj
(deftype ObjectsMapEntry [key omap]
clojure.lang.IMapEntry
(key [_] key)
(getKey [_] key)
(val [_]
(get omap key))
(getValue [_]
(get omap key))
clojure.lang.IHashEq
(hasheq [_]
(-hash-for-key omap key))))
#?(:clj
(deftype ObjectsMapIterator [^Iterator iterator omap]
Iterator
(hasNext [_]
(.hasNext iterator))
(next [_]
(let [entry (.next iterator)]
(ObjectsMapEntry. (key entry) omap)))))
#?(:clj
(deftype ObjectsMap [metadata
^:unsynchronized-mutable buffer
^:unsynchronized-mutable header
^:unsynchronized-mutable data
^:unsynchronized-mutable initialized
^:unsynchronized-mutable modified
^:unsynchronized-mutable hash
^:unsynchronized-mutable index
^:unsynchronized-mutable cache]
Object
(hashCode [this]
(.hasheq ^clojure.lang.IHashEq this))
cp/Datafiable
(datafy [this]
{:buffer buffer
:initialized initialized
:modified modified
:hash hash
:index index
:cache cache})
IObjectsMap
(-initialize [this]
(locking this
(when-not initialized
(let [index' (read-index header)
cache' (if *lazy*
{}
(read-objects data index'))]
(set! (.-index this) index')
(set! (.-cache this) cache')
(set! (.-initialized this) true)))))
(-compact [this]
(locking this
(when modified
(do-compact buffer header data index cache
(fn [buffer' header' data' index']
(set! (.-modified this) false)
(set! (.-buffer this) buffer')
(set! (.-header this) header')
(set! (.-data this) data')
(set! (.-index this) index'))))))
(-hash-for-key [this key]
(if (contains? cache key)
(c/hash (get cache key))
(let [pos (val (get index key))]
(buf/read-int data pos))))
(-get-buffer [this]
(-compact this)
buffer)
json/JSONWriter
(-write [this writter options]
(json/-write (into {} this) writter options))
clojure.lang.IHashEq
(hasheq [this]
(when-not hash
(set! hash (Murmur3/hashUnordered this)))
hash)
clojure.lang.Seqable
(seq [this]
(when-not initialized (-initialize this))
(RT/chunkIteratorSeq (.iterator ^Iterable this)))
java.lang.Iterable
(iterator [this]
(when-not initialized (-initialize this))
(ObjectsMapIterator. (.iterator ^Iterable index) this))
IPersistentCollection
(equiv [this other]
(and (instance? ObjectsMap other)
(= (count this) (count other))
(reduce-kv (fn [result id _]
(let [this-val (get this id)
other-val (get other id)
result (= this-val other-val)]
(or result
(reduced false))))
true
index)))
clojure.lang.IPersistentMap
(cons [this o]
(when-not initialized (-initialize this))
(if (map-entry? o)
(assoc this (key o) (val o))
(if (vector? o)
(assoc this (nth o 0) (nth o 1))
(throw (UnsupportedOperationException. "invalid arguments to cons")))))
(empty [_]
(create))
(containsKey [this key]
(when-not initialized (-initialize this))
(contains? index key))
(entryAt [this key]
(when-not initialized (-initialize this))
(ObjectsMapEntry. this key))
(valAt [this key]
(when-not initialized (-initialize this))
(locking this
(if (contains? cache key)
(get cache key)
(if (contains? index key)
(let [[size pos] (get index key)
bval (buf/read-bytes data (+ pos 4) (- size 4))
oval (t/decode bval)]
(set! (.-cache this) (assoc cache key oval))
oval)
(do
(set! (.-cache this) (assoc cache key nil))
nil)))))
(valAt [this key not-found]
(when-not initialized (-initialize this))
(if (.containsKey ^IPersistentMap index key)
(.valAt this key)
not-found))
(assoc [this key val]
(when-not initialized (-initialize this))
(when-not (instance? UUID key)
(throw (IllegalArgumentException. "key should be an instance of UUID")))
(ObjectsMap. metadata
buffer
header
data
true
true
nil
(assoc index key nil)
(assoc cache key val)))
(assocEx [_ _ _]
(throw (UnsupportedOperationException. "method not implemented")))
(without [this key]
(when-not initialized (-initialize this))
(ObjectsMap. metadata
buffer
header
data
true
true
nil
(dissoc index key)
(dissoc cache key)))
clojure.lang.Counted
(count [this]
(when-not initialized (-initialize this))
(count index))))
(defn- read-index
[header]
(persistent!
(reduce (fn [result i]
(let [offset (* i RECORD-SIZE)
key (buf/read-uuid header (+ offset 0))
size (buf/read-int header (+ offset 16))
cpos (buf/read-int header (+ offset 20))]
(assoc! result key (d/vec2 size cpos))))
(transient {})
(-> (/ (buf/size header) RECORD-SIZE)
(long)
(range)))))
(defn- read-objects
[data index]
(persistent!
(reduce-kv (fn [result key [size pos]]
(let [bbuf (buf/read-bytes data
(+ pos 4)
(- size 4))]
(assoc! result (t/decode bbuf))))
(transient {})
index)))
(defn- do-compact
[buffer header data index cache update-fn]
(let [[new-data new-hash data-size]
(loop [entries (seq index)
data-size 0
new-data {}
new-hash {}]
(if-let [entry (first entries)]
(let [[key [size :as entry-value]] entry]
(if (nil? entry-value)
(let [nval (get cache key)
hash (c/hash nval)
bval (t/encode nval)
size (+ (alength ^bytes bval) 4)]
(recur (rest entries)
(+ data-size size)
(assoc new-data key bval)
(assoc new-hash key hash)))
(recur (rest entries)
(+ data-size ^long size)
new-data
new-hash)))
[new-data new-hash data-size]))
header-size
(* (count index) RECORD-SIZE)
buffer-size
(+ 4 header-size data-size)
buffer'
(buf/allocate buffer-size)
header'
(buf/slice buffer' 4 header-size)
data'
(buf/slice buffer'
(+ 4 header-size)
data-size)
index'
(loop [entries (seq index)
position 0
offset 0
index {}]
(if-let [[key [size prev-pos :as value-entry]] (first entries)]
(do
(buf/write-uuid header' (+ offset 0) key)
(buf/write-int header' (+ offset 20) position)
(if (nil? value-entry)
(let [bval (get new-data key)
hval (get new-hash key)
size (+ (alength ^bytes bval) 4)]
(buf/write-int header' (+ offset 16) size)
(buf/write-int data' (+ position 0) hval)
(buf/write-bytes data' (+ position 4) bval (- size 4))
(recur (rest entries)
(+ position ^long size)
(+ offset ^long RECORD-SIZE)
(assoc index key (d/vec2 size position))))
(do
(buf/write-int header' (+ offset 16) size)
(buf/copy-bytes data prev-pos size data' position)
(recur (rest entries)
(+ position ^long size)
(+ offset ^long RECORD-SIZE)
(assoc index key (d/vec2 size position))))))
index))]
(buf/write-int buffer' 0 header-size)
(update-fn buffer' header' data' index')
nil))
(defn from-buffer
[buffer]
(let [header-size (buf/read-int buffer 0)
header (buf/slice buffer 4 header-size)
data (buf/slice buffer
(+ 4 header-size)
(- (buf/size buffer)
(+ 4 header-size)))]
(ObjectsMap. {}
buffer
header
data
false
false
nil
nil
nil)))
(defn from-bytes
[barray]
(from-buffer (buf/wrap barray)))
(defn objects-map?
[o]
(instance? ObjectsMap o))
(defn create
([]
(let [buf (buf/allocate 4)]
(buf/write-i32 buf 0 0)
(from-buffer buf)))
([other]
(cond
(objects-map? other)
(let [buf (-get-buffer other)
buf (buf/clone buf)]
(from-buffer buf))
:else
(throw (UnsupportedOperationException. "invalid arguments")))))
(defn wrap
[objects]
(if (instance? ObjectsMap objects)
objects
(let [result (into (create) objects)]
(-compact result)
result)))
(fres/add-handlers!
{:name "penpot/objects-map/v3"
:class ObjectsMap
:wfn (fn [n w o]
(fres/write-tag! w n)
(fres/write-bytes! w (.array ^ByteBuffer (-get-buffer o))))
:rfn (fn [r]
(-> r fres/read-object! from-bytes))})
(t/add-handlers!
{:id "penpot/objects-map/v3"
:class ObjectsMap
:wfn (fn [^ObjectsMap o]
(let [buffer (-get-buffer o)]
#?(:cljs (js/Uint8Array. (.-buffer ^js/DataView buffer))
:clj (.array ^ByteBuffer buffer))))
:rfn (fn [o]
(from-bytes o))})
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.common.types.transit-map
(:require
#?(:clj [app.common.fressian :as fres])
#?(:clj [clojure.data.json :as json])
[app.common.transit :as t]
[clojure.core :as c]
[clojure.core.protocols :as cp])
#?(:clj
(:import
clojure.lang.RT
#?(:clj (set! *warn-on-reflection* true))
(declare create)
(defprotocol ITransitMap
(^:no-doc initialize [this])
(^:no-doc compact [this])
(^:no-doc get-data [this] "retrieve internal data"))
#?(:cljs
(deftype TransitMap [metadata cache
^:mutable data
^:mutable modified]
Object
(toString [this]
(pr-str* this))
(equiv [this other]
(c/-equiv this other))
(keys [this]
(c/es6-iterator (keys this)))
(entries [this]
(c/es6-entries-iterator (seq this)))
(values [this]
(es6-iterator (vals this)))
(has [this k]
(c/contains? this k))
(get [this k not-found]
(c/-lookup this k not-found))
(forEach [this f]
(run! (fn [[k v]] (f v k)) this))
cp/Datafiable
(datafy [_]
{:data data
:cache cache
:modified modified})
ITransitMap
(initialize [this]
(if-not ^boolean cache
(let [data' (t/decode-str data)]
(set! (.-cache this) data')
data)
cache))
(compact [this]
(when-not ^boolean cache
(set! (.-cache this) (t/decode-str data)))
(when ^boolean modified
(set! (.-modified this) false)
(set! (.-data this) (t/encode-str cache)))
this)
(get-data [this]
(compact this)
data)
c/IWithMeta
(-with-meta [this new-meta]
(if (identical? new-meta meta)
this
(TransitMap. new-meta
cache
data
modified)))
c/IMeta
(-meta [_] metadata)
c/ICloneable
(-clone [this]
(compact this)
(TransitMap. metadata {} data false))
c/IIterable
(-iterator [this]
(c/seq-iter this))
c/ICollection
(-conj [this entry]
(let [data (-> (initialize this)
(c/-conj entry))]
(TransitMap. metadata data nil true)))
c/IEmptyableCollection
(-empty [_]
(create))
c/IEquiv
(-equiv [this other]
(-> (initialize this)
(equiv-map other)))
c/IHash
(-hash [this]
(c/-hash (initialize this)))
c/ISeqable
(-seq [this]
(c/-seq (initialize this)))
c/ICounted
(-count [this]
(c/-count (initialize this)))
c/ILookup
(-lookup [this k]
(c/-lookup (initialize this) k))
(-lookup [this k not-found]
(c/-lookup (initialize this) k not-found))
c/IAssociative
(-assoc [this k v]
(let [data (-> (initialize this)
(c/-assoc k v))]
(TransitMap. metadata data nil true)))
(-contains-key? [this k]
(c/-contains-key? (initialize this) k))
c/IFind
(-find [this k]
(c/-find (initialize this) k))
c/IMap
(-dissoc [this k]
(let [data (-> (initialize this)
(c/-dissoc k))]
(TransitMap. metadata data nil true)))
c/IKVReduce
(-kv-reduce [this f init]
(c/-kv-reduce (initialize this) f init))
c/IFn
(-invoke [this k]
(c/-lookup this k))
(-invoke [this k not-found]
(c/-lookup this k not-found))
c/IPrintWithWriter
(-pr-writer [this writer opts]
(c/pr-sequential-writer
writer
(fn [item w _]
(c/-write w (pr-str (c/-key item)))
(c/-write w \space)
(c/-write w (pr-str (c/-val item))))
"#penpot/transit-map {" ", " "}"
opts
(seq this))))
:clj
(deftype TransitMap [metadata
^:volatile-mutable cache
^:unsynchronized-mutable data
^:volatile-mutable modified]
Object
(hashCode [this]
(.hasheq ^clojure.lang.IHashEq this))
cp/Datafiable
(datafy [_]
{:data data
:cache cache
:modified modified})
ITransitMap
(initialize [this]
(if-not ^boolean cache
(let [data' (if (nil? data) {} (t/decode-str data))]
(set! (.-cache this) data')
data')
cache))
(compact [this]
(when ^boolean modified
(set! (.-modified this) false)
(set! (.-data this) (t/encode-str cache)))
this)
(get-data [this]
(compact this)
data)
json/JSONWriter
(-write [this writter options]
(json/-write (into {} this) writter options))
clojure.lang.IHashEq
(hasheq [this]
(.hasheq ^clojure.lang.IHashEq (initialize this)))
clojure.lang.Seqable
(seq [this]
(clojure.lang.RT/chunkIteratorSeq (.iterator ^Iterable this)))
java.lang.Iterable
(iterator [this]
(.iterator ^java.lang.Iterable (initialize this)))
clojure.lang.IPersistentCollection
(equiv [this other]
(and (instance? TransitMap other)
(= (count this) (count other))
(reduce-kv (fn [_ id this-val]
(let [other-val (get other id)
result (= this-val other-val)]
(or result
(reduced false))))
true
cache)))
clojure.lang.IPersistentMap
(cons [this o]
(let [data (.cons ^clojure.lang.IPersistentMap (initialize this) o)]
(TransitMap. metadata data nil true)))
(empty [_]
(create))
(containsKey [this key]
(.containsKey ^clojure.lang.IPersistentMap (initialize this) key))
(entryAt [this key]
(.entryAt ^clojure.lang.IPersistentMap (initialize this) key))
(valAt [this key]
(.valAt ^clojure.lang.IPersistentMap (initialize this) key))
(valAt [this key not-found]
(.valAt ^clojure.lang.IPersistentMap (initialize this) key not-found))
(assoc [this key val]
(let [data (.assoc ^clojure.lang.IPersistentMap (initialize this) key val)]
(TransitMap. metadata data nil true)))
(assocEx [_ _ _]
(throw (UnsupportedOperationException. "method not implemented")))
(without [this key]
(let [data (.without ^clojure.lang.IPersistentMap (initialize this) key)]
(TransitMap. metadata data nil true)))
clojure.lang.Counted
(count [this]
(.count ^clojure.lang.Counted (initialize this)))))
#?(:cljs (es6-iterable TransitMap))
(defn from-data
[data]
(TransitMap. {} nil data false))
(defn transit-map?
[o]
(instance? TransitMap o))
(defn create
([] (from-data nil))
([other]
(cond
(transit-map? other)
(-> other get-data from-data)
:else
(throw #?(:clj (UnsupportedOperationException. "invalid arguments")
:cljs (js/Error. "invalid arguments"))))))
(defn wrap
[objects]
(if (instance? TransitMap objects)
objects
(->> objects
(into (create))
#_(compact))))
#?(:clj
(fres/add-handlers!
{:name "penpot/transit-map/v2"
:class TransitMap
:wfn (fn [n w o]
(fres/write-tag! w n)
(fres/write-object! w (get-data o)))
:rfn (fn [r]
(-> r fres/read-object! from-data))}))
(t/add-handlers!
{:id "penpot/transit-map/v2"
:class TransitMap
:wfn get-data
:rfn from-data})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment