Created
September 13, 2015 08:43
-
-
Save nsf/c8eeb0d1819ee469472d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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