Skip to content

Instantly share code, notes, and snippets.

@nsf
Created September 13, 2015 08:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nsf/c8eeb0d1819ee469472d to your computer and use it in GitHub Desktop.
Save nsf/c8eeb0d1819ee469472d to your computer and use it in GitHub Desktop.
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