Skip to content

Instantly share code, notes, and snippets.

@nsf
Created September 13, 2015 08:43
namespace NG.FS.VoxelMap.Components
open System
open System.IO
open System.Diagnostics
open System.Collections.Generic
open NG.FS.VoxelMap.Common
open NG.FS.Math
open NG.FS.Coroutines
open NG.FS.ArrayUtils
open NG
type RequestType =
| Read
| Write
type Operation =
| Grab
| Release
type Request = {
Type : RequestType
Location : Vec2i
Size : Vec2i
Data : C.MapChunkColumn[,]
}
type DeferredRequest = {
Request : Request
Coroutine : Coroutine
}
[<Struct>]
type ChunkColumnInfo =
val mutable Readers : int
member this.HasWriter = this.Readers = -1
member this.HasReaders = this.Readers > 0
// Location is in storage chunks
type StorageChunk (location : Vec2i) =
let storageChunk = C.MapStorageChunk.New()
let fileName = sprintf "%08X_%08X.ngc" location.X location.Y
member val ColumnsInfo = createArray2D STORAGE_CHUNK_SIZE.XY (ChunkColumnInfo(Readers=0))
member sc.Save (dir : string) = routine {
storageChunk.Serialize()
do! coroutine_p(Type = IO) { storageChunk.SaveData(Path.Combine(dir, fileName)) }
}
member sc.Load (dir : string) = routine {
let! loaded = coroutine_p(Type = IO) { return storageChunk.LoadData(Path.Combine(dir, fileName)) }
if loaded then
storageChunk.Deserialize()
return loaded
}
member sc.Generate () = routine {
let baseLoc = storageChunkLocationToChunkColumnLocation location
let generateColumn (loc : Vec2i) = coroutine {
C.MapGenerator.GenerateColumn(storageChunk.Column(loc), baseLoc + loc)
}
do! initFlatArray2D STORAGE_CHUNK_SIZE.XY generateColumn
}
member sc.Column (p : Vec2i) = storageChunk.Column(p)
member sc.Dispose () = (sc :> IDisposable).Dispose()
interface IDisposable with
member sc.Dispose () = storageChunk.Dispose()
type Storage (dir : string) =
let storageChunks = Dictionary<Vec2i, StorageChunk option>()
let deferredRequests = List<DeferredRequest>()
let mutex = Mutex()
let addSelfToDeferredRequests r =
let c = Scheduler.CurrentCoroutine()
let dr = { Request = r; Coroutine = c }
deferredRequests.Add(dr)
let applyRequest (r : Request) (op : Operation) =
let mutator =
match op with
| Grab ->
match r.Type with
| Read -> fun (info : ChunkColumnInfo) ->
ChunkColumnInfo(Readers = info.Readers+1)
| Write -> fun (info : ChunkColumnInfo) ->
ChunkColumnInfo(Readers = -1)
| Release ->
match r.Type with
| Read -> fun (info : ChunkColumnInfo) ->
ChunkColumnInfo(Readers = info.Readers-1)
| Write -> fun (info : ChunkColumnInfo) ->
ChunkColumnInfo(Readers = 0)
seqBase2D r.Location r.Size |> Seq.iter (fun p ->
// storage chunk location for this chunk column
let scloc = chunkColumnLocationToStorageChunkLocation p
// chunk column location relative to storage chunk (that is location inside the storage chunk)
let scp = p - (storageChunkLocationToChunkColumnLocation scloc)
match storageChunks.[scloc] with
| Some sc ->
sc.ColumnsInfo.[scp.Y, scp.X] <- mutator sc.ColumnsInfo.[scp.Y, scp.X]
| None -> failwith "never happens")
// The last parameter is used to queue loading of a storage chunk (given location).
// Usually you need to do so when request arrives, but not for deferred requests.
// Also it breaks cyclic dependency.
let trySatisfyRequest (r : Request) (queueStorageChunk : Vec2i -> unit) =
let result = ref true
seqBase2D r.Location r.Size |> Seq.iter (fun p ->
// storage chunk location for this chunk column
let scloc = chunkColumnLocationToStorageChunkLocation p
// chunk column location relative to storage chunk (that is location inside the storage chunk)
let scp = p - (storageChunkLocationToChunkColumnLocation scloc)
// chunk column location relative to result array
let rp = p - r.Location
match storageChunks.TryGetValue(scloc) with
| true, Some sc ->
let info = sc.ColumnsInfo.[scp.Y, scp.X]
match r.Type with
| Read ->
if info.HasWriter then
result := false
| Write ->
if info.HasReaders then
result := false
r.Data.[rp.Y, rp.X] <- sc.Column scp
| true, None ->
result := false
| false, _ ->
// Here we should also spawn the loading coroutine
result := false
storageChunks.[scloc] <- None
queueStorageChunk scloc
)
if !result then
applyRequest r Grab
!result
let trySatisfyDeferredRequest (dr : DeferredRequest) =
if trySatisfyRequest dr.Request ignore then
Scheduler.Go dr.Coroutine
true
else
false
let trySatisfyDeferredRequests () =
deferredRequests.RemoveAll (Predicate trySatisfyDeferredRequest) |> ignore
// 1. Does loading/generation.
// 2. Locks the mutex.
// 3. Writes result to storageChunks dict
// Note: caller should write "None" to storageChunks before executing this coroutine.
let loadOrGenerateStorageChunk (loc : Vec2i) = coroutine {
let sc = new StorageChunk(loc)
let! loaded = sc.Load(dir)
if not loaded then
do! sc.Generate()
do! lockMutex(mutex)
storageChunks.[loc] <- Some sc
trySatisfyDeferredRequests()
}
let queueStorageChunk (loc : Vec2i) =
Scheduler.Go (loadOrGenerateStorageChunk loc)
// Location and size is in chunk columns
member this.AcquireChunkColumns (r : RequestType) (loc : Vec2i) (size : Vec2i) = coroutine {
do! lockMutex(mutex)
let req = { Type = r; Location = loc; Size = size; Data = zeroCreateArray2D size }
if not (trySatisfyRequest req queueStorageChunk) then
// If request cannot be satisfied immediately, we will queue ourselves
// and sleep. Eventually somebody will satisfy it.
addSelfToDeferredRequests req
do! sleep()
return req
}
member this.ReleaseChunkColumns (r : Request) = coroutine {
do! lockMutex(mutex)
applyRequest r Release
trySatisfyDeferredRequests()
}
type ChunkMeshType =
| GraphicsOnly
| PhysicsAndGraphics
[<AllowNullLiteral>]
type ChunkMesh (t : ChunkMeshType) =
let mutable refCount = 1
let chunkMesh = C.MapChunkMesh.New()
member this.RefCount = refCount
member this.ChunkMesh = chunkMesh
member this.VerticesCount = chunkMesh.VerticesCount()
member val Type = t
member val VerticesOffset = 0 with get, set
member this.Dispose () = (this :> IDisposable).Dispose()
interface IDisposable with
member this.Dispose () =
refCount <- refCount - 1
if refCount = 0 then
chunkMesh.Dispose()
member this.Mesh (columns : C.MapChunkColumn[,]) (z : int) =
use a = C.MapChunkColumnArray.New(4)
a.SetNth(0, columns.[0, 0])
a.SetNth(1, columns.[0, 1])
a.SetNth(2, columns.[1, 0])
a.SetNth(3, columns.[1, 1])
chunkMesh.Mesh(a, z)
member this.Grab () =
refCount <- refCount + 1
this
type Segment (segment : C.MapSegment, location : Vec2i, storage : Storage) =
let mutable current = zeroCreateArray3D<ChunkMesh> SEGMENT_SIZE
let mutable next = zeroCreateArray3D<ChunkMesh> SEGMENT_SIZE
let mutable lastPlayerLocation = Vec3i Int32.MaxValue // in chunks
let getChunkMeshType (player : Vec3i) (p : Vec3i) =
let d = Vec3i.ChebyshevDistance player p
if d <= 2 then PhysicsAndGraphics else GraphicsOnly
let swap () =
let tmp = current
current <- next
next <- tmp
next |> Array3D.iter (fun cm -> if not (isNull cm) then cm.Dispose())
fillArray3D next null
let chunkMeshUpload (cm : ChunkMesh) = coroutine {
let mutable msg = C.Messages.MapChunkMeshAppend.New()
msg.Segment <- segment
msg.ChunkMesh <- cm.ChunkMesh
do! msg
cm.VerticesOffset <- msg.OutVerticesOffset
msg.Dispose()
}
let chunkMeshBuildAndUpload (ap : Vec3i) (cm : ChunkMesh) = coroutine {
let! r = storage.AcquireChunkColumns Read (ap.XY - Vec2i 1) (Vec2i 2)
cm.Mesh r.Data ap.Z
do! storage.ReleaseChunkColumns r
if cm.VerticesCount <> 0 then
let mutable msg = C.Messages.MapChunkMeshAppend.New()
msg.Segment <- segment
msg.ChunkMesh <- cm.ChunkMesh
do! msg
cm.VerticesOffset <- msg.OutVerticesOffset
msg.Dispose()
else
cm.VerticesOffset <- 0
}
member private this.RebuildSegment (min : Vec3i) (max : Vec3i) = routine {
let baseLoc = segmentLocationToChunkLocation location
let buildAndUpload =
seqBase3D (Vec3i 0) SEGMENT_SIZE
|> Seq.filter (fun p -> min .<= p && p .<= max)
|> Seq.choose (fun p ->
let ap = baseLoc + p
let cmtype = getChunkMeshType lastPlayerLocation ap
let cm = current.[p.Z, p.Y, p.X]
if not (isNull cm) && cm.Type = cmtype then
next.[p.Z, p.Y, p.X] <- cm.Grab()
None
else
let cm = new ChunkMesh(cmtype)
next.[p.Z, p.Y, p.X] <- cm
Some (chunkMeshBuildAndUpload ap cm)
)
let upload =
seqBase3D (Vec3i 0) SEGMENT_SIZE
|> Seq.filter (fun p -> let cm = next.[p.Z, p.Y, p.X] in not (isNull cm) && cm.RefCount = 2)
|> Seq.map (fun p -> chunkMeshUpload next.[p.Z, p.Y, p.X])
do! upload
do! buildAndUpload
seqBase3D (Vec3i 0) SEGMENT_SIZE
|> Seq.iter (fun p ->
let cm = next.[p.Z, p.Y, p.X]
if not (isNull cm) && cm.VerticesCount <> 0 then
segment.AppendInfo(p, cm.VerticesOffset, cm.VerticesCount)
)
let mutable msg = C.Messages.MapSegmentSwap.New()
msg.Segment <- segment
do! sendMessageAndDispose msg
swap()
}
member this.Segment = segment
member this.Location = location
// all Vec3i parameters are in chunks
member this.RebuildSegmentMaybe (player : Vec3i) (min : Vec3i) (max : Vec3i) = coroutine {
if lastPlayerLocation <> player then
lastPlayerLocation <- player
do! this.RebuildSegment min max
}
member this.Dispose () = (this :> IDisposable).Dispose()
interface IDisposable with
member this.Dispose () =
segment.Dispose()
type Map (storage : Storage) =
let map = C.Map.New();
let segments = Dictionary<Vec2i, Segment>();
let viewDistance = 400
let mutex = Mutex()
let mutable lastPlayerLocation = Vec3i Int32.MaxValue // in chunks
member private this.CreateSegment (loc : Vec2i) = coroutine {
let mutable msg = C.Messages.MapSegmentNew.New()
msg.Location <- loc
do! msg
let s = new Segment(msg.OutSegment, loc, storage)
msg.Dispose()
return s
}
member private this.Rebuild () = routine {
let viewChunks = viewDistance / CHUNK_SIZE_I
let visRange = Vec3i(viewChunks * 2, viewChunks * 2, STORAGE_CHUNK_SIZE.Z)
let halfVisRange = visRange / Vec3i 2
let min = (lastPlayerLocation - halfVisRange) * Vec3i(1, 1, 0)
let max = min + visRange - Vec3i(1)
let sbase = chunkLocationToSegmentLocation min
let ssize = chunkLocationToSegmentLocation (max - min + Vec3i 1)
let! newSegments = seqBase2D sbase ssize |> Seq.choose (fun p ->
match segments.TryGetValue(p) with
| false, _ -> Some (this.CreateSegment p)
| _ -> None
)
newSegments |> Array.iter (fun seg -> segments.Add(seg.Location, seg))
do! seqBase2D sbase ssize |> Seq.map (fun p -> segments.[p].RebuildSegmentMaybe lastPlayerLocation min max)
let v = map.Segments()
segments.Values |> Seq.iter (fun s -> v.Add(s.Segment))
let mutable msg = C.Messages.MapSwap.New()
msg.Map <- map
do! sendMessageAndDispose msg
}
member this.Map = map
member this.RebuildMaybe (player : Vec3i) = coroutine {
let sw = Stopwatch()
sw.Start()
if lastPlayerLocation <> player then
lastPlayerLocation <- player
do! this.Rebuild()
sw.Stop()
printfn "map update done in: %A" sw.Elapsed
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment