Skip to content

Instantly share code, notes, and snippets.

@kerams
Last active December 14, 2021 14:05
Show Gist options
  • Save kerams/eb03fdf1b918b8bee9c90c900b3e36cf to your computer and use it in GitHub Desktop.
Save kerams/eb03fdf1b918b8bee9c90c900b3e36cf to your computer and use it in GitHub Desktop.
module Format =
[<Literal>]
let Nil = 0xc0uy
[<Literal>]
let False = 0xc2uy
[<Literal>]
let True = 0xc3uy
let inline fixposnum value = byte value
let inline fixnegnum value = byte value ||| 0b11100000uy
[<Literal>]
let Uint8 = 0xccuy
[<Literal>]
let Uint16 = 0xcduy
[<Literal>]
let Uint32 = 0xceuy
[<Literal>]
let Uint64 = 0xcfuy
[<Literal>]
let Int8 = 0xd0uy
[<Literal>]
let Int16 = 0xd1uy
[<Literal>]
let Int32 = 0xd2uy
[<Literal>]
let Int64 = 0xd3uy
let inline fixstr len = 160uy + byte len
[<Literal>]
let Str8 = 0xd9uy
[<Literal>]
let Str16 = 0xdauy
[<Literal>]
let Str32 = 0xdbuy
[<Literal>]
let Float32 = 0xcauy
[<Literal>]
let Float64 = 0xcbuy
let inline fixarr len = 144uy + byte len
[<Literal>]
let Array16 = 0xdcuy
[<Literal>]
let Array32 = 0xdduy
[<Literal>]
let Bin8 = 0xc4uy
[<Literal>]
let Bin16 = 0xc5uy
[<Literal>]
let Bin32 = 0xc6uy
let inline fixmap len = 128uy + byte len
[<Literal>]
let Map16 = 0xdeuy
[<Literal>]
let Map32 = 0xdfuy
module Read =
open System
open System.Text
open System.Collections
open System.Collections.Concurrent
open System.Collections.Generic
open FSharp.Reflection
open System.Reflection
let interpretStringAs (typ: Type) (str: string) =
#if FABLE_COMPILER
box str
#else
if typ = typeof<string> then
box str
elif typ = typeof<char> then
box str.[0]
else
// todo cacheable
// String enum
let case = FSharpType.GetUnionCases (typ, true) |> Array.find (fun y -> y.Name = str)
FSharpValue.MakeUnion (case, [||], true)
#endif
let inline interpretIntegerAs typ n =
//if typ = typeof<Int32> then int32 n |> box
if Object.ReferenceEquals (typ, typeof<Int32>) then int32 n |> box
elif typ = typeof<Int64> then int64 n |> box
elif typ = typeof<Int16> then int16 n |> box
elif typ = typeof<UInt32> then uint32 n |> box
elif typ = typeof<UInt64> then uint64 n |> box
elif typ = typeof<UInt16> then uint16 n |> box
elif typ = typeof<TimeSpan> then TimeSpan (int64 n) |> box
#if NET6_0_OR_GREATER
elif typ = typeof<DateOnly> then DateOnly.FromDayNumber (int32 n) |> box
elif typ = typeof<TimeOnly> then TimeOnly (int64 n) |> box
#endif
#if FABLE_COMPILER
elif typ.FullName = "Microsoft.FSharp.Core.int16`1" then int16 n |> box
elif typ.FullName = "Microsoft.FSharp.Core.int32`1" then int32 n |> box
elif typ.FullName = "Microsoft.FSharp.Core.int64`1" then int64 n |> box
#endif
elif typ = typeof<byte> then byte n |> box
elif typ = typeof<sbyte> then sbyte n |> box
#if !FABLE_COMPILER
elif typ.IsEnum then Enum.ToObject (typ, int64 n)
#else
elif typ.IsEnum then float n |> box
#endif
else failwithf "Cannot interpret integer %A as %s." n typ.Name
let inline interpretFloatAs typ n =
box n
#if !FABLE_COMPILER
type DictionaryDeserializer<'k,'v when 'k: equality and 'k: comparison> () =
static let keyType = typeof<'k>
static let valueType = typeof<'v>
static member Deserialize (len: int, isDictionary, read: Type -> obj) =
if isDictionary then
let dict = Dictionary<'k, 'v> (len)
for _ in 0 .. len - 1 do
dict.Add (read keyType :?> 'k, read valueType :?> 'v)
box dict
else
Array.init len (fun _ -> read keyType :?> 'k, read valueType :?> 'v)
|> Map.ofArray
|> box
type ListDeserializer<'a> () =
static let argType = typeof<'a>
static member Deserialize (len: int, read: Type -> obj) =
List.init len (fun _ -> read argType :?> 'a)
|> box
type SetDeserializer<'a when 'a : comparison> () =
static let argType = typeof<'a>
static member Deserialize (len: int, read: Type -> obj) =
let mutable set = Set.empty
for _ in 0 .. len - 1 do
set <- set.Add (read argType :?> 'a)
set
|> box
#endif
type Reader (data: byte[]) =
let mutable pos = 0
let intBuf = Array.zeroCreate 8
#if !FABLE_COMPILER
static let arrayReaderCache = ConcurrentDictionary<Type, (int * Reader) -> obj> ()
static let mapReaderCache = ConcurrentDictionary<Type, (int * Reader) -> obj> ()
static let setReaderCache = ConcurrentDictionary<Type, (int * Reader) -> obj> ()
static let unionConstructorCache = ConcurrentDictionary<UnionCaseInfo, obj [] -> obj> ()
static let unionCaseFieldCache = ConcurrentDictionary<Type * int, UnionCaseInfo * Type[]> ()
#endif
let readInt len m =
if BitConverter.IsLittleEndian then
for i in 0 .. len - 1 do
intBuf.[i] <- data.[pos + len - 1 - i]
pos <- pos + len
m (intBuf, 0)
else
pos <- pos + len
m (data, pos - len)
member inline _.ReadByte () =
pos <- pos + 1
data.[pos - 1]
member _.ReadRawBin len =
pos <- pos + len
data.[ pos - len .. pos - 1 ]
member _.ReadString len =
pos <- pos + len
Encoding.UTF8.GetString (data, pos - len, len)
member x.ReadUInt8 () =
x.ReadByte ()
member x.ReadInt8 () =
x.ReadByte () |> sbyte
member _.ReadUInt16 () =
readInt 2 BitConverter.ToUInt16
member _.ReadInt16 () =
readInt 2 BitConverter.ToInt16
member _.ReadUInt32 () =
pos <- pos + 4
uint32 (data.[pos - 4] <<< 24) ||| uint32 (data.[pos - 3] <<< 16) ||| uint32 (data.[pos - 2] <<< 8) ||| uint32 data.[pos - 1]
//readInt 4 BitConverter.ToUInt32
member _.ReadInt32 () =
readInt 4 BitConverter.ToInt32
member _.ReadUInt64 () =
readInt 8 BitConverter.ToUInt64
//pos <- pos + 8
//uint64 (data.[pos - 8] <<< 56) ||| uint64 (data.[pos - 7] <<< 48) ||| uint64 (data.[pos - 6] <<< 40) ||| uint64 (data.[pos - 5] <<< 32) |||
//uint64 (data.[pos - 4] <<< 24) ||| uint64 (data.[pos - 3] <<< 16) ||| uint64 (data.[pos - 2] <<< 8) ||| uint64 data.[pos - 1]
member _.ReadInt64 () =
readInt 8 BitConverter.ToInt64
member x.ReadFloat32 () =
//let mutable b = x.ReadInt32 ()
readInt 4 BitConverter.ToSingle
(*let sign = if (b >>> 31) = 0 then 1f else -1f
let mutable e = (b >>> 23) &&& 0xff
let m = b &&& 0x7fffff
let m =
if e = 0 then
if m = 0 then
0f
else
e <- e - 126
1.192093037616359652020037174224853515625f
else
e <- e - 127
1f + float32 m / 8388608f
sign * m * float32 (Math.Pow (2., float e))*)
member x.ReadFloat64 () =
readInt 8 BitConverter.ToDouble
// let mutable b = x.ReadInt64 ()
// let sign = if (b >>> 63) = 0L then 1. else -1.
// let mutable e = (b >>> 52) &&& 0x07ffL
// let m = b &&& 0x0fffffffffffffL
// let m =
// if e = 0L then
// if m = 0L then
// 0.
// else
// e <- e - 1022L
// 1. / float 0x0fffffffffffffL
// else
// e <- e - 1023L
// 1. + float m / (float 0x10000000000000L)
// sign * m * (Math.Pow (2., float e))
member x.ReadMap (len: int, t: Type) =
#if !FABLE_COMPILER
mapReaderCache.GetOrAdd (t, Func<_, _>(fun (t: Type) ->
let args = t.GetGenericArguments ()
if args.Length <> 2 then
failwithf "Expecting %s, but the data contains a map." t.Name
let mapDeserializer = typedefof<DictionaryDeserializer<_,_>>.MakeGenericType args
let isDictionary = t.GetGenericTypeDefinition () = typedefof<Dictionary<_, _>>
let d = Delegate.CreateDelegate (typeof<Func<int, bool, (Type -> obj), obj>>, mapDeserializer.GetMethod "Deserialize") :?> Func<int, bool, (Type -> obj), obj>
fun (len, x: Reader) -> d.Invoke (len, isDictionary, x.Read))) (len, x)
#else
let args = t.GetGenericArguments ()
if args.Length <> 2 then
failwithf "Expecting %s, but the data contains a map." t.Name
let pairs =
let arr = Array.zeroCreate len
for i in 0 .. len - 1 do
arr.[i] <- x.Read args.[0] |> box :?> IStructuralComparable, x.Read args.[1]
arr
if t.GetGenericTypeDefinition () = typedefof<Dictionary<_, _>> then
let dict = Dictionary<_, _> len
pairs |> Array.iter dict.Add
box dict
else
Map.ofArray pairs |> box
#endif
member x.ReadSet (len: int, t: Type) =
#if !FABLE_COMPILER
setReaderCache.GetOrAdd (t, Func<_, _>(fun (t: Type) ->
let args = t.GetGenericArguments ()
if args.Length <> 1 then
failwithf "Expecting %s, but the data contains a set." t.Name
let setDeserializer = typedefof<SetDeserializer<_>>.MakeGenericType args
let d = Delegate.CreateDelegate (typeof<Func<int, (Type -> obj), obj>>, setDeserializer.GetMethod "Deserialize") :?> Func<int, (Type -> obj), obj>
fun (len, x: Reader) -> d.Invoke (len, x.Read))) (len, x)
#else
let args = t.GetGenericArguments ()
if args.Length <> 1 then
failwithf "Expecting %s, but the data contains a set." t.Name
let mutable set = Set.empty
for _ in 0 .. len - 1 do
set <- set.Add(x.Read args.[0] |> box :?> IStructuralComparable)
box set
#endif
member x.ReadRawArray (len: int, elementType: Type) =
#if !FABLE_COMPILER
let arr = Array.CreateInstance (elementType, len)
for i in 0 .. len - 1 do
arr.SetValue (x.Read elementType, i)
arr
#else
let arr = Array.zeroCreate len
for i in 0 .. len - 1 do
arr.[i] <- x.Read elementType
arr
#endif
member x.ReadArray (len, t) =
#if !FABLE_COMPILER
match arrayReaderCache.TryGetValue t with
| true, reader ->
reader (len, x)
| _ ->
#endif
if FSharpType.IsRecord t then
#if !FABLE_COMPILER
let fieldTypes = FSharpType.GetRecordFields t |> Array.map (fun prop -> prop.PropertyType)
let ctor = FSharpValue.PreComputeRecordConstructor (t, true)
arrayReaderCache.GetOrAdd (t, fun (_, x: Reader) ->
ctor (fieldTypes |> Array.map x.Read)) (len, x)
#else
let props = FSharpType.GetRecordFields t
FSharpValue.MakeRecord (t, props |> Array.map (fun prop -> x.Read prop.PropertyType))
#endif
elif FSharpType.IsUnion (t, true) then
#if !FABLE_COMPILER
if t.IsGenericType && t.GetGenericTypeDefinition () = typedefof<_ list> then
let argType = t.GetGenericArguments () |> Array.head
let listDeserializer = typedefof<ListDeserializer<_>>.MakeGenericType argType
let d = Delegate.CreateDelegate (typeof<Func<int, (Type -> obj), obj>>, listDeserializer.GetMethod "Deserialize") :?> Func<int, (Type -> obj), obj>
arrayReaderCache.GetOrAdd (t, fun (len, (x: Reader)) -> d.Invoke (len, x.Read)) (len, x)
else
arrayReaderCache.GetOrAdd (t, fun (_, x: Reader) ->
let tag = x.Read typeof<int> :?> int
let case, fieldTypes =
unionCaseFieldCache.GetOrAdd ((t, tag), fun (t, tag) ->
let case = FSharpType.GetUnionCases (t, true) |> Array.find (fun x -> x.Tag = tag)
let fields = case.GetFields ()
case, fields |> Array.map (fun x -> x.PropertyType))
let fields =
// single parameter is serialized directly, not in an array, saving 1 byte on the array format
if fieldTypes.Length = 1 then
[| x.Read fieldTypes.[0] |]
else
// don't care about this byte, it's going to be a fixarr of length fieldTypes.Length
x.ReadByte () |> ignore
fieldTypes |> Array.map x.Read
unionConstructorCache.GetOrAdd (case, Func<_, _>(fun case -> FSharpValue.PreComputeUnionConstructor (case, true))) fields) (len, x)
#else
let tag = x.Read typeof<int> :?> int
let case = FSharpType.GetUnionCases (t, true) |> Array.find (fun x -> x.Tag = tag)
let fieldTypes = case.GetFields () |> Array.map (fun x -> x.PropertyType)
let fields =
// single parameter is serialized directly, not in an array, saving 1 byte on the array format
if fieldTypes.Length = 1 then
[| x.Read fieldTypes.[0] |]
else
// don't care about this byte, it's going to be a fixarr of length fieldTypes.Length
x.ReadByte () |> ignore
fieldTypes |> Array.map x.Read
FSharpValue.MakeUnion (case, fields, true)
#endif
#if FABLE_COMPILER // Fable does not recognize Option as a union
elif t.IsGenericType && t.GetGenericTypeDefinition () = typedefof<Option<_>> then
let tag = x.ReadByte ()
// none case
if tag = 0uy then
x.ReadByte () |> ignore
box null
else
x.Read (t.GetGenericArguments () |> Array.head) |> Some |> box
elif t.IsGenericType && t.GetGenericTypeDefinition () = typedefof<_ list> then
let elementType = t.GetGenericArguments () |> Array.head
[
for _ in 0 .. len - 1 ->
x.Read elementType
] |> box
#endif
elif t.IsArray then
x.ReadRawArray (len, t.GetElementType ()) |> box
elif FSharpType.IsTuple t then
#if !FABLE_COMPILER
let elementTypes = FSharpType.GetTupleElements t
let tupleCtor = FSharpValue.PreComputeTupleConstructor t
arrayReaderCache.GetOrAdd (t, fun (_, (x: Reader)) -> elementTypes |> Array.map x.Read |> tupleCtor) (len, x)
#else
FSharpValue.MakeTuple (FSharpType.GetTupleElements t |> Array.map x.Read, t)
#endif
elif t = typeof<DateTime> then
let dateTimeTicks = x.Read typeof<int64> :?> int64
let kindAsInt = x.Read typeof<int64> :?> int64
let kind =
match kindAsInt with
| 1L -> DateTimeKind.Utc
| 2L -> DateTimeKind.Local
| _ -> DateTimeKind.Unspecified
DateTime(ticks=dateTimeTicks, kind=kind) |> box
elif t = typeof<DateTimeOffset> then
let dateTimeTicks = x.Read typeof<int64> :?> int64
let timeSpanMinutes = x.Read typeof<int16> :?> int16
DateTimeOffset (dateTimeTicks, TimeSpan.FromMinutes (float timeSpanMinutes)) |> box
elif t.IsGenericType && t.GetGenericTypeDefinition () = typedefof<Set<_>> then
x.ReadSet(len, t)
#if !FABLE_COMPILER
elif t = typeof<System.Data.DataTable> then
match x.ReadRawArray(2, typeof<string>) :?> string array with
| [|schema;data|] ->
let t = new System.Data.DataTable()
t.ReadXmlSchema(new System.IO.StringReader(schema))
t.ReadXml(new System.IO.StringReader(data)) |> ignore
box t
| otherwise -> failwithf "Expecting %s at position %d, but the data contains an array." t.Name pos
elif t = typeof<System.Data.DataSet> then
match x.ReadRawArray(2, typeof<string>) :?> string array with
| [|schema;data|] ->
let t = new System.Data.DataSet()
t.ReadXmlSchema(new System.IO.StringReader(schema))
t.ReadXml(new System.IO.StringReader(data)) |> ignore
box t
| otherwise -> failwithf "Expecting %s at position %d, but the data contains an array." t.Name pos
#endif
elif t = typeof<decimal> || t.FullName = "Microsoft.FSharp.Core.decimal`1" then
#if !FABLE_COMPILER
arrayReaderCache.GetOrAdd (t, fun (_, (x: Reader)) -> x.ReadRawArray (4, typeof<int>) :?> int[] |> Decimal |> box) (len, x)
#else
x.ReadRawArray (4, typeof<int>) |> box :?> int[] |> Decimal |> box
#endif
else
failwithf "Expecting %s at position %d, but the data contains an array." t.Name pos
member x.ReadBin (len, t) =
if t = typeof<Guid> then
x.ReadRawBin len |> Guid |> box
elif t = typeof<byte[]> then
x.ReadRawBin len |> box
elif t = typeof<bigint> then
x.ReadRawBin len |> bigint |> box
else
failwithf "Expecting %s at position %d, but the data contains bin." t.Name pos
member x.Read t =
match x.ReadByte () with
// fixstr
| b when b ||| 0b00011111uy = 0b10111111uy -> b &&& 0b00011111uy |> int |> x.ReadString |> interpretStringAs t
| Format.Str8 -> x.ReadByte () |> int |> x.ReadString |> interpretStringAs t
| Format.Str16 -> x.ReadUInt16 () |> int |> x.ReadString |> interpretStringAs t
| Format.Str32 -> x.ReadUInt32 () |> int |> x.ReadString |> interpretStringAs t
// fixposnum
| b when b ||| 0b01111111uy = 0b01111111uy -> interpretIntegerAs t b
// fixnegnum
| b when b ||| 0b00011111uy = 0b11111111uy -> sbyte b |> interpretIntegerAs t
| Format.Int64 -> x.ReadInt64 () |> interpretIntegerAs t
| Format.Int32 -> x.ReadInt32 () |> interpretIntegerAs t
| Format.Int16 -> x.ReadInt16 () |> interpretIntegerAs t
| Format.Int8 -> x.ReadInt8 () |> interpretIntegerAs t
| Format.Uint8 -> x.ReadUInt8 () |> interpretIntegerAs t
| Format.Uint16 -> x.ReadUInt16 () |> interpretIntegerAs t
| Format.Uint32 -> x.ReadUInt32 () |> interpretIntegerAs t
| Format.Uint64 -> x.ReadUInt64 () |> interpretIntegerAs t
| Format.Float32 -> x.ReadFloat32 () |> interpretFloatAs t
| Format.Float64 -> x.ReadFloat64 () |> interpretFloatAs t
| Format.Nil -> box null
| Format.True -> box true
| Format.False -> box false
// fixarr
| b when b ||| 0b00001111uy = 0b10011111uy -> x.ReadArray (b &&& 0b00001111uy |> int, t)
| Format.Array16 ->
let len = x.ReadUInt16 () |> int
x.ReadArray (len, t)
| Format.Array32 ->
let len = x.ReadUInt32 () |> int
x.ReadArray (len, t)
// fixmap
| b when b ||| 0b00001111uy = 0b10001111uy -> x.ReadMap (b &&& 0b00001111uy |> int, t)
| Format.Map16 ->
let len = x.ReadUInt16 () |> int
x.ReadMap (len, t)
| Format.Map32 ->
let len = x.ReadUInt32 () |> int
x.ReadMap (len, t)
| Format.Bin8 ->
let len = x.ReadByte () |> int
x.ReadBin (len, t)
| Format.Bin16 ->
let len = x.ReadUInt16 () |> int
x.ReadBin (len, t)
| Format.Bin32 ->
let len = x.ReadUInt32 () |> int
x.ReadBin (len, t)
| b ->
failwithf "Position %d, byte %d, expected type %s." pos b t.Name
module Write =
open System.IO
open System
open System.Collections.Generic
open System.Text
open FSharp.Reflection
open FSharp.NativeInterop
open System.Reflection
open System.Collections.Concurrent
module Fable =
let private serializerCache = Dictionary<string, obj -> ResizeArray<byte> -> unit> ()
let private cacheGetOrAdd (typ: Type, f) =
match serializerCache.TryGetValue typ.FullName with
| true, f -> f
| _ ->
serializerCache.Add (typ.FullName, f)
f
let inline private write32bitNumber b1 b2 b3 b4 (out: ResizeArray<byte>) writeFormat =
if b2 > 0uy || b1 > 0uy then
if writeFormat then out.Add Format.Uint32
out.Add b1
out.Add b2
out.Add b3
out.Add b4
elif (b3 > 0uy) then
if writeFormat then out.Add Format.Uint16
out.Add b3
out.Add b4
else
if writeFormat then out.Add Format.Uint8
out.Add b4
let private write64bitNumber b1 b2 b3 b4 b5 b6 b7 b8 (out: ResizeArray<byte>) =
if b4 > 0uy || b3 > 0uy || b2 > 0uy || b1 > 0uy then
out.Add Format.Uint64
out.Add b1
out.Add b2
out.Add b3
out.Add b4
out.Add b5
out.Add b6
out.Add b7
out.Add b8
else
write32bitNumber b5 b6 b7 b8 out true
let inline private writeUnsigned32bitNumber (n: UInt32) (out: ResizeArray<byte>) =
write32bitNumber (n >>> 24 |> byte) (n >>> 16 |> byte) (n >>> 8 |> byte) (byte n) out
let inline private writeUnsigned64bitNumber (n: UInt64) (out: ResizeArray<byte>) =
write64bitNumber (n >>> 56 |> byte) (n >>> 48 |> byte) (n >>> 40 |> byte) (n >>> 32 |> byte) (n >>> 24 |> byte) (n >>> 16 |> byte) (n >>> 8 |> byte) (byte n) out
let inline private writeNil (out: ResizeArray<byte>) = out.Add Format.Nil
let inline private writeBool x (out: ResizeArray<byte>) = out.Add (if x then Format.True else Format.False)
let private writeSignedNumber bytes (out: ResizeArray<byte>) =
if BitConverter.IsLittleEndian then
Array.rev bytes |> out.AddRange
else
out.AddRange bytes
let private writeUInt64 (n: UInt64) (out: ResizeArray<byte>) =
if n < 128UL then
out.Add (Format.fixposnum n)
else
writeUnsigned64bitNumber n out
let private writeInt64 (n: int64) (out: ResizeArray<byte>) =
if n >= 0L then
writeUInt64 (uint64 n) out
else
if n > -32L then
out.Add (Format.fixnegnum n)
else
//todo length optimization
out.Add Format.Int64
writeSignedNumber (BitConverter.GetBytes n) out
let private writeByte b (out: ResizeArray<byte>) =
if b < 128uy then
out.Add (Format.fixposnum b)
else
out.Add Format.Uint8
out.Add b
let inline private writeString (str: string) (out: ResizeArray<byte>) =
let str = Encoding.UTF8.GetBytes str
if str.Length < 32 then
out.Add (Format.fixstr str.Length)
else
if str.Length < 256 then
out.Add Format.Str8
elif str.Length < 65536 then
out.Add Format.Str16
else
out.Add Format.Str32
writeUnsigned32bitNumber (uint32 str.Length) out false
out.AddRange str
let private writeSingle (n: float32) (out: ResizeArray<byte>) =
out.Add Format.Float32
writeSignedNumber (BitConverter.GetBytes n) out
let private writeDouble (n: float) (out: ResizeArray<byte>) =
out.Add Format.Float64
writeSignedNumber (BitConverter.GetBytes n) out
let private writeBin (data: byte[]) (out: ResizeArray<byte>) =
if data.Length < 256 then
out.Add Format.Bin8
elif data.Length < 65536 then
out.Add Format.Bin16
else
out.Add Format.Bin32
writeUnsigned32bitNumber (uint32 data.Length) out false
out.AddRange data
let inline private writeDateTime (out: ResizeArray<byte>) (dto: DateTime) =
out.Add (Format.fixarr 2uy)
writeInt64 dto.Ticks out
writeInt64 (int64 dto.Kind) out
let inline private writeDateTimeOffset (out: ResizeArray<byte>) (dto: DateTimeOffset) =
out.Add (Format.fixarr 2uy)
writeInt64 dto.Ticks out
writeInt64 (int64 dto.Offset.TotalMinutes) out
#if NET6_0_OR_GREATER
let inline private writeDateOnly (out: ResizeArray<byte>) (date: DateOnly) =
writeUnsigned32bitNumber (uint32 date.DayNumber) out true
let inline private writeTimeOnly (out: ResizeArray<byte>) (time: TimeOnly) =
writeUInt64 (uint64 time.Ticks) out
#endif
let private writeArrayHeader len (out: ResizeArray<byte>) =
if len < 16 then
out.Add (Format.fixarr len)
elif len < 65536 then
out.Add Format.Array16
out.Add (len >>> 8 |> FSharp.Core.Operators.byte)
out.Add (FSharp.Core.Operators.byte len)
else
out.Add Format.Array32
writeUnsigned32bitNumber (uint32 len) out false
let private writeDecimal (n: decimal) (out: ResizeArray<byte>) =
let bits = Decimal.GetBits n
writeArrayHeader bits.Length out
for b in bits do
writeUnsigned32bitNumber (uint32 b) out true
let rec private writeArray (out: ResizeArray<byte>) t (arr: System.Collections.ICollection) =
writeArrayHeader arr.Count out
for x in arr do
writeObject x t out
and private writeMap (out: ResizeArray<byte>) keyType valueType (dict: IDictionary<obj, obj>) =
let length = dict.Count
if length < 16 then
out.Add (Format.fixmap length)
elif length < 65536 then
out.Add Format.Map16
out.Add (length >>> 8 |> FSharp.Core.Operators.byte)
out.Add (FSharp.Core.Operators.byte length)
else
out.Add Format.Map32
writeUnsigned32bitNumber (uint32 length) out false
for kvp in dict do
writeObject kvp.Key keyType out
writeObject kvp.Value valueType out
and private writeSet (out: ResizeArray<byte>) t (set: System.Collections.ICollection) =
writeArrayHeader set.Count out
for x in set do
writeObject x t out
and inline private writeRecord (out: ResizeArray<byte>) (types: Type[]) (vals: obj[]) =
writeArrayHeader vals.Length out
for i in 0 .. vals.Length - 1 do
writeObject vals.[i] types.[i] out
and inline private writeTuple (out: ResizeArray<byte>) (types: Type[]) (vals: obj[]) =
writeRecord out types vals
and private writeUnion (out: ResizeArray<byte>) tag (types: Type[]) (vals: obj[]) =
out.Add (Format.fixarr 2uy)
out.Add (Format.fixposnum tag)
// save 1 byte if the union case has a single parameter
if vals.Length <> 1 then
writeArrayHeader vals.Length out
for i in 0 .. vals.Length - 1 do
writeObject vals.[i] types.[i] out
else
writeObject vals.[0] types.[0] out
and writeObject (x: obj) (t: Type) (out: ResizeArray<byte>) =
#if !FABLE_COMPILER
raise (NotSupportedException "This function is meant to be used in Fable, please use serializeObj or makeSerializer.")
#else
if isNull x then writeNil out else
match serializerCache.TryGetValue (t.FullName) with
| true, writer ->
writer x out
| _ ->
if FSharpType.IsRecord (t, true) then
let fieldTypes = FSharpType.GetRecordFields (t, true) |> Array.map (fun x -> x.PropertyType)
cacheGetOrAdd (t, fun x out -> writeRecord out fieldTypes (FSharpValue.GetRecordFields (x, true))) x out
elif t.IsArray then
let elementType = t.GetElementType ()
cacheGetOrAdd (t, fun x out -> writeArray out elementType (x :?> System.Collections.ICollection)) x out
elif FSharpType.IsUnion (t, true) then
cacheGetOrAdd (t, fun x out ->
let case, fields = FSharpValue.GetUnionFields (x, t, true)
let fieldTypes = case.GetFields () |> Array.map (fun x -> x.PropertyType)
writeUnion out case.Tag fieldTypes fields) x out
elif FSharpType.IsTuple t then
let fieldTypes = FSharpType.GetTupleElements t
cacheGetOrAdd (t, fun x out -> writeTuple out fieldTypes (FSharpValue.GetTupleFields x)) x out
elif t.IsEnum then
cacheGetOrAdd (t, fun x -> writeInt64 (box x :?> int64)) x out
elif t.IsGenericType then
let tDef = t.GetGenericTypeDefinition()
let genArgs = t.GetGenericArguments ()
if tDef = typedefof<_ list> then
let elementType = genArgs |> Array.head
cacheGetOrAdd (t, fun x out -> writeArray out elementType (x :?> System.Collections.ICollection)) x out
elif tDef = typedefof<_ option> then
cacheGetOrAdd (t, fun x out ->
let opt = x :?> _ option
let tag, value = if Option.isSome opt then 1, opt.Value else 0, null
writeUnion out tag genArgs [| value |]) x out
elif tDef = typedefof<Dictionary<_, _>> || tDef = typedefof<Map<_, _>> then
let keyType = genArgs.[0]
let valueType = genArgs.[1]
cacheGetOrAdd (t, fun x out -> writeMap out keyType valueType (box x :?> IDictionary<obj, obj>)) x out
elif tDef = typedefof<Set<_>> then
let elementType = genArgs |> Array.head
cacheGetOrAdd (t, fun x out -> writeSet out elementType (x :?> System.Collections.ICollection)) x out
else
failwithf "Cannot serialize %s." t.Name
elif t.FullName = "Microsoft.FSharp.Core.int16`1" || t.FullName = "Microsoft.FSharp.Core.int32`1" || t.FullName = "Microsoft.FSharp.Core.int64`1" then
cacheGetOrAdd (t, fun x out -> writeInt64 (x :?> int64) out) x out
elif t.FullName = "Microsoft.FSharp.Core.decimal`1" then
cacheGetOrAdd (t, fun x out -> writeDecimal (x :?> decimal) out) x out
elif t.FullName = "Microsoft.FSharp.Core.float`1" then
cacheGetOrAdd (t, fun x out -> writeDouble (x :?> float) out) x out
elif t.FullName = "Microsoft.FSharp.Core.float32`1" then
cacheGetOrAdd (t, fun x out -> writeSingle (x :?> float32) out) x out
else
failwithf "Cannot serialize %s." t.Name
#endif
let inline writeType<'T> (x: 'T) (out: ResizeArray<byte>) =
#if !FABLE_COMPILER
raise (NotSupportedException "This function is meant to be used in Fable, please use serializeObj or makeSerializer.")
#else
writeObject x typeof<'T> out
#endif
#if FABLE_COMPILER
serializerCache.Add (typeof<byte>.FullName, fun x out -> writeByte (x :?> byte) out)
serializerCache.Add (typeof<sbyte>.FullName, fun x out -> writeByte (x :?> sbyte |> byte) out)
serializerCache.Add (typeof<unit>.FullName, fun _ out -> writeNil out)
serializerCache.Add (typeof<bool>.FullName, fun x out -> writeBool (x :?> bool) out)
serializerCache.Add (typeof<char>.FullName, fun x out -> writeString (x :?> string) out) // There are only strings in JS
serializerCache.Add (typeof<string>.FullName, fun x out -> writeString (x :?> string) out)
serializerCache.Add (typeof<int>.FullName, fun x out -> writeInt64 (x :?> int |> int64) out)
serializerCache.Add (typeof<int16>.FullName, fun x out -> writeInt64 (x :?> int16 |> int64) out)
serializerCache.Add (typeof<int64>.FullName, fun x out -> writeInt64 (x :?> int64) out)
serializerCache.Add (typeof<UInt32>.FullName, fun x out -> writeUInt64 (x :?> UInt32 |> uint64) out)
serializerCache.Add (typeof<UInt16>.FullName, fun x out -> writeUInt64 (x :?> UInt16 |> uint64) out)
serializerCache.Add (typeof<UInt64>.FullName, fun x out -> writeUInt64 (x :?> UInt64) out)
serializerCache.Add (typeof<float32>.FullName, fun x out -> writeSingle (x :?> float32) out)
serializerCache.Add (typeof<float>.FullName, fun x out -> writeDouble (x :?> float) out)
serializerCache.Add (typeof<decimal>.FullName, fun x out -> writeDecimal (x :?> decimal) out)
serializerCache.Add (typeof<byte[]>.FullName, fun x out -> writeBin (x :?> byte[]) out)
serializerCache.Add (typeof<bigint>.FullName, fun x out -> writeBin ((x :?> bigint).ToByteArray ()) out)
serializerCache.Add (typeof<Guid>.FullName, fun x out -> writeBin ((x :?> Guid).ToByteArray ()) out)
serializerCache.Add (typeof<DateTime>.FullName, fun x out -> writeDateTime out (x :?> DateTime))
serializerCache.Add (typeof<DateTimeOffset>.FullName, fun x out -> writeDateTimeOffset out (x :?> DateTimeOffset))
#if NET6_0_OR_GREATER
serializerCache.Add (typeof<DateOnly>.FullName, fun x out -> writeDateOnly out (x :?> DateOnly))
serializerCache.Add (typeof<TimeOnly>.FullName, fun x out -> writeTimeOnly out (x :?> TimeOnly))
#endif
serializerCache.Add (typeof<TimeSpan>.FullName, fun x out -> writeInt64 (x :?> TimeSpan).Ticks out)
#endif
open System
let time f x =
let s = DateTime.Now
f x |> ignore
printfn "%A" (DateTime.Now - s).TotalMilliseconds
let a = ResizeArray ()
let x = [| for i in 0 .. 1_000_000 -> 500000 |], [| for i in 0 .. 0 -> float i |]
time (fun () -> Write.Fable.writeObject x typeof<int32[] * float[]> a) ()
let b = a |> Seq.toArray
time (fun () ->
let r = Read.Reader(b)
r.Read(typeof<int32[] * float[]>) |> ignore) ()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment