myou-engine/libs/arr_ref/slice_mem.nim

204 lines
6.8 KiB
Nim

## SliceMem represents a slice of memory, but it includes a reference to the
## original container (of any type) or even a custom destructor, so memory is
## properly freed when it's no longer in use.
##
## It's called "SliceMem" because "MemSlice" is taken by std/memfiles.
import std/strformat
import std/hashes
import std/macros
type
SliceMem*[T] = object
data*: ptr UncheckedArray[T]
byte_len*: int
destroy_ref: ref CustomDestructor
CustomDestructor = object
destroy: proc() {.closure, raises: [].}
proc `=destroy`(s: var CustomDestructor) =
if s.destroy != nil:
s.destroy()
s.destroy = nil
template toInt(p: pointer): int = cast[int](p)
# template toPointer(i: int): pointer = cast[pointer](p)
template toPointer*(s: SliceMem): pointer =
s.data.pointer
proc newSliceMem*[T, U](container: sink U; p: pointer, byte_len: int): SliceMem[T] =
## Create a SliceMem from a container, a pointer, and a length in bytes.
result = SliceMem[T](
data: cast[ptr UncheckedArray[T]](p),
byte_len: byte_len,
destroy_ref: (ref CustomDestructor)(destroy: proc()=
# we don't actually need to destroy the container here, destroying
# the closure will do it for us. Calling it allows us to use custom
# destructors though.
discard container
),
)
proc newSliceMem*[T](p: ptr T, byte_len: int, destructor: proc() {.closure, raises: [].}): SliceMem[T] =
## Create a SliceMem from a pointer to a type, a length in bytes, and a
## destructor closure.
result = SliceMem[T](
data: cast[ptr UncheckedArray[T]](p),
byte_len: byte_len,
destroy_ref: (ref CustomDestructor)(destroy: destructor),
)
proc newSliceMem*(p: pointer, byte_len: int, destructor: proc() {.closure, raises: [].}): SliceMem[byte] {.inline.} =
newSliceMem(cast[ptr byte](p), byte_len, destructor)
template newSliceMem*(container: not pointer, p, byte_len: untyped): untyped =
## Template to automatically determine the type for `newSliceMem[T,U]`
newSliceMem[typeof(container.items)](container, p, byte_len)
proc newSliceMem*[T, U](container: sink U; first, last: pointer): SliceMem[T] =
## Create a SliceMem from a container and the two pointers of the first and
## last elements in the container. Usually you will want to use `toSliceMem`
## instead.
result = SliceMem[T](
data: cast[ptr UncheckedArray[T]](first),
byte_len: (last.toInt -% first.toInt) + sizeof(T),
destroy_ref: (ref CustomDestructor)(destroy: proc()=
discard container
),
)
proc newSliceMem*[T](size: int): SliceMem[T] =
## Create a SliceMem from new memory, similar to ArrRef[T].
var r: ref byte
unsafeNew(r, size * sizeof(T))
result = SliceMem[T](
data: cast[ptr UncheckedArray[T]](r),
byte_len: size * sizeof(T),
destroy_ref: (ref CustomDestructor)(destroy: proc()=
discard r
),
)
macro noMove(e: untyped): untyped =
if e.kind == nnkCommand and e[0].repr == "move": e[1]
else: e
template toSliceMem*(container, slice: untyped): untyped =
## Create a SliceMem from a container and a slice that indicates the range.
## The container needs to have `[]` and `items()` (like `seq`). If it doesn't
## you need to pass pointers directly to `newSliceMem` instead.
if slice.len != 0:
# TODO: check that with boundChecks:off it doesn't try to read the value
let first = noMove(container)[slice.a].addr
let last = noMove(container)[slice.b].addr
newSliceMem[typeof(noMove(container).items)](container, first, last)
else:
SliceMem[typeof(container.items)]()
template toSliceMem*(container): untyped =
## Create a SliceMem from a container, with all its contents. The container
## needs to have `[]` and `items()` (like `seq`). If it doesn't you need to
## pass pointers directly to `newSliceMem` instead.
let s = noMove(container).low .. noMove(container).high
toSliceMem(container, s)
template len*[T](s: SliceMem[T]): Natural =
s.byte_len div sizeof(T)
template low*[T](s: SliceMem[T]): Natural = 0
template high*[T](s: SliceMem[T]): int = s.len - 1
proc `[]`*[T](s: SliceMem[T], i: Natural): var T {.inline.} =
when compileOption("rangechecks"):
if i >= s.len:
raise newException(RangeDefect, &"index out of range: {i} >= {s.len}")
s.data[i]
proc `[]=`*[T](s: SliceMem[T], i: Natural, v: T) {.inline.} =
when compileOption("rangechecks"):
if i >= s.len:
raise newException(RangeDefect, &"index out of range: {i} >= {s.len}")
s.data[i] = v
template `[]`*[T](s: SliceMem[T], i: BackwardsIndex): T =
s[s.len - i.int]
template `[]=`*[T](s: SliceMem[T], i: BackwardsIndex, v: T) =
s[s.len - i.int] = v
template `[]`*[T](s: SliceMem[T], i: BackwardsIndex): T =
s[s.len - i.int]
template `[]=`*[T](s: SliceMem[T], i: BackwardsIndex, v: T) =
s[s.len - i.int] = v
template `[]`*[T](a: SliceMem[T], s: Slice[int]): var SliceMem[T] =
toSliceMem(a, s)
iterator items*[T](s: SliceMem[T]): T =
for i in 0 ..< s.len:
yield s.data[i]
iterator pairs*[T](s: SliceMem[T]): tuple[key: int, val: T] =
for i in 0 ..< s.len:
yield (i, s.data[i])
iterator mitems*[T](s: SliceMem[T]): var T =
for i in 0 ..< s.len:
yield s.data[i]
iterator mpairs*[T](s: SliceMem[T]): tuple[key: int, val: var T] =
for i in 0 ..< s.len:
yield (i, s.data[i])
proc `$`*[T](s: SliceMem[T]): string =
result = "SliceMem(["
if s.byte_len != 0:
let hi = s.high
for i in 0 ..< hi:
result &= $s.data[i] & ", "
result &= $s.data[hi]
result &= "])"
proc hash*[T](s: SliceMem[T]): Hash =
# Make use of stdlib's murmur3
hash(cast[ptr UncheckedArray[byte]](s.data).toOpenArray(0, s.byte_len - 1))
template toOpenArray*(s: SliceMem): untyped =
s.data.toOpenArray(0, s.byte_len - 1)
template toOpenArrayByte*(s: SliceMem): untyped =
cast[ptr UncheckedArray[byte]](s.data).toOpenArray(0, s.byte_len - 1)
template to*[T](s: SliceMem, typ: typedesc[T]): SliceMem[T] =
cast[SliceMem[T]](s)
template to*[T](s: seq[SliceMem], typ: typedesc[T]): seq[SliceMem[T]] =
var s2 = newSeqOfCap[SliceMem[T]](s.len)
for slice in s:
s2.add cast[SliceMem[T]](slice)
s2
template concatImpl(slices: untyped) =
var total = 0
for s in slices:
total += s.len
result = newSliceMem[T](total)
var offset = 0
for s in slices:
copyMem(result[offset].addr, s.data, s.len * sizeof(T))
offset += s.len
proc concat*[T](slices: varargs[SliceMem[T]]): SliceMem[T] =
concatImpl(slices)
proc concat*[T](slices: seq[SliceMem[T]]): SliceMem[T] =
concatImpl(slices)
template `&`*[T,U](a: SliceMem[T], b: SliceMem[U]): SliceMem[T] =
concat(a, b.to(T))