diff --git a/CHANGELOG.md b/CHANGELOG.md index 13f264bc8..27bba32f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,6 @@ - Adds command buffers support for deferred structural operations (#670). - Adds the possibility to specify that a query column is read-only (#677). -- Adds `DiskVector` and `DiskStructArray` to use the disk for components in very large simulations (#656). - Adds `GPUVector{:CPU}` and `GPUStructArray{:CPU}` to use a GPU storage on devices which do not have a GPU (#701). - Adds the `boxed` keyword argument to the world constructor, which cuts the compilation cost of Ark, at the price of some slowdown in performance. diff --git a/Project.toml b/Project.toml index cf0470165..f97a089e2 100644 --- a/Project.toml +++ b/Project.toml @@ -6,9 +6,7 @@ authors = ["Martin Lange ", "Adriano Meligrana Storage{Vector}, Velocity => Storage{StructArray}, - Health => Storage{DiskVector}, + Health, ) # output diff --git a/src/Ark.jl b/src/Ark.jl index 614adafef..46e0010ed 100644 --- a/src/Ark.jl +++ b/src/Ark.jl @@ -2,15 +2,12 @@ module Ark using Adapt using FieldViews -using FileWatching -using Mmap using Preferences using StaticArrays using FunctionWrappers: FunctionWrapper using Random const THREAD_SAFE_LOCK = @load_preference("THREAD_SAFE_LOCK", default = "true") -const TMP_ARK_DIR = @load_preference("TMP_ARK_DIR", default = joinpath(@__DIR__, "tmp_ark")) isdefined(@__MODULE__, :Memory) || const Memory = Vector # Compat for Julia < 1.11 @@ -22,8 +19,6 @@ include("collections.jl") include("structarray.jl") include("gpu_vector.jl") include("gpu_structarray.jl") -include("disk_vector.jl") -include("disk_structarray.jl") include("structarray_interface.jl") include("fieldsview.jl") include("entity.jl") @@ -53,11 +48,6 @@ include("unchecked.jl") include("command_buffer.jl") !_is_testing() && include("precompile.jl") -function __init__() - _sweep_stale_ark_sessions!() - return nothing -end - #include("docs.jl") # doctest setup export World @@ -96,6 +86,6 @@ export SetComponentsCommand, SetRelationsCommand export Relation -export Storage, StructArray, GPUStructArray, GPUVector, DiskVector, DiskStructArray +export Storage, StructArray, GPUStructArray, GPUVector end diff --git a/src/abstract.jl b/src/abstract.jl index b760ea460..b94dda801 100644 --- a/src/abstract.jl +++ b/src/abstract.jl @@ -28,8 +28,8 @@ struct Relation{T} end Storage{T} Marks component types for using `T` as a [storage](@ref component-storages) in the -world constructor. Built-in storages include `Vector`, `StructArray`, `DiskVector`, -`DiskStructArray`, `GPUVector` and `GPUStructArray`. +world constructor. Built-in storages include `Vector`, `StructArray`, `GPUVector` +and `GPUStructArray`. If, during world construction, the storage mode is not specified, it defaults to `Storage{Vector}`. diff --git a/src/disk_structarray.jl b/src/disk_structarray.jl deleted file mode 100644 index b26b5852a..000000000 --- a/src/disk_structarray.jl +++ /dev/null @@ -1,86 +0,0 @@ -""" - DiskStructArray - -A disk-backed StructArray that stores each component field in a [`DiskVector`](@ref). - -As for `DiskVector`, backing files are managed by Ark and deleted automatically -when the storage is garbage-collected or at process exit. All component fields -must be isbits types with nonzero size. - -# Examples - -```julia -world = World( - Position => Storage{DiskStructArray}, - Velocity => Storage{DiskStructArray}, -) -``` -""" -struct DiskStructArray{C,CS<:NamedTuple,N} <: _AbstractStructArray{C,CS,N} - _components::CS -end - -function _check_diskstructarray_type(::Type{C}) where {C} - if fieldcount(C) == 0 - throw(ArgumentError("DiskStructArray storage not allowed for components without fields")) - end - for t in fieldtypes(C) - _check_diskvector_eltype(t) - end - return nothing -end - -function DiskStructArray(tp::Type{C}) where {C} - _DiskStructArray_from_type(tp) -end - -@generated function _DiskStructArray_from_type(::Type{C}) where {C} - names = fieldnames(C) - types = fieldtypes(C) - num_fields = length(types) - vec_types = Expr[:(DiskVector{$t}) for t in types] - nt_type = :(NamedTuple{$names,Tuple{$(vec_types...)}}) - kv_exprs = Expr[:($name = DiskVector{$t}()) for (name, t) in zip(names, types)] - return quote - _check_diskstructarray_type(C) - DiskStructArray{C,$nt_type,$num_fields}((; $(kv_exprs...))) - end -end - -@generated function _DiskStructArray_type(::Type{C}) where {C} - names = fieldnames(C) - types = fieldtypes(C) - num_fields = length(types) - vec_types = Expr[:(DiskVector{$t}) for t in types] - nt_type = :(NamedTuple{$names,Tuple{$(vec_types...)}}) - return quote - DiskStructArray{C,$nt_type,$num_fields} - end -end - -@generated function _DiskStructArrayView_type(::Type{C}, ::Type{I}) where {C,I<:AbstractUnitRange{T}} where {T<:Integer} - names = fieldnames(C) - types = fieldtypes(C) - subarray_types = Expr[:(SubArray{$t,1,DiskVector{$t},Tuple{I},true}) for t in types] - nt_type = :(NamedTuple{ - $names, - Tuple{$(subarray_types...)}, - }) - return quote - StructArrayView{C,$nt_type} - end -end - -@generated function Base.view( - sa::S, - idx::I, -) where {S<:DiskStructArray{C,CS,N},I<:AbstractUnitRange{T}} where {C,CS<:NamedTuple,N,T<:Integer} - names = fieldnames(C) - vec_types = fieldtypes(CS) - view_exprs = Expr[:($name = @view getfield(sa, :_components).$name[idx]) for name in names] - subarray_types = Expr[:(SubArray{$(eltype(vt)),1,$vt,Tuple{I},true}) for vt in vec_types] - nt_type = :(NamedTuple{$names,Tuple{$(subarray_types...)}}) - return quote - StructArrayView{C,$nt_type}((; $(view_exprs...))) - end -end diff --git a/src/disk_vector.jl b/src/disk_vector.jl deleted file mode 100644 index 0e1a23a4c..000000000 --- a/src/disk_vector.jl +++ /dev/null @@ -1,239 +0,0 @@ -""" - DiskVector - -A disk-backed vector implementation for isbits component storage. - -`DiskVector` uses a temporary memory-mapped file as backing storage. - -Files are managed by Ark and deleted automatically when the vector is -garbage-collected or at process exit. Files left behind by a process -that crashed are removed by the next Julia session that loads Ark. -""" -mutable struct DiskVector{T} <: AbstractVector{T} - path::String - mem::Vector{T} - len::Int - capacity::Int -end - -const DISKVECTOR_MEMORY_LENGTH = 128 - -const _ARK_SESSION_DIR = Ref{String}() -const _ARK_SESSION_DIR_LOCK = ReentrantLock() -const _ARK_SESSION_REGEX = r"^ark_session_(\d+)_" - -macro tryonly(ex) - quote - try - $(esc(ex)) - catch - nothing - end - end -end - -function _ark_session_dir() - lock(_ARK_SESSION_DIR_LOCK) do - if !isassigned(_ARK_SESSION_DIR) - mkpath(TMP_ARK_DIR) - _ARK_SESSION_DIR[] = mktempdir(TMP_ARK_DIR; prefix="ark_session_$(getpid())_", cleanup=true) - end - return _ARK_SESSION_DIR[] - end -end - -function isvalidpid(hostname::AbstractString, pid::Integer) - (pid <= 0 || pid > typemax(Cuint)) && return false - pid == getpid() && return true - return FileWatching.Pidfile.isvalidpid(hostname, Cuint(pid)) -end - -function _sweep_stale_ark_sessions!() - isdir(TMP_ARK_DIR) || return nothing - host = gethostname() - for entry in readdir(TMP_ARK_DIR) - m = match(_ARK_SESSION_REGEX, entry) - captures = m.captures - if m !== nothing && captures !== nothing - pid = tryparse(Int, captures[1]::SubString{String}) - if pid !== nothing && isvalidpid(host, pid) - continue - end - end - @tryonly rm(joinpath(TMP_ARK_DIR, entry); recursive=true, force=true) - end - return nothing -end - -function _check_diskvector_eltype(::Type{T}) where {T} - if !isbitstype(T) - throw(ArgumentError("DiskVector storage requires an isbits component type, got $(nameof(T))")) - elseif sizeof(T) == 0 - throw(ArgumentError("DiskVector storage requires a nonzero-size component type, got $(nameof(T))")) - end - return nothing -end - -function DiskVector{T}() where {T} - _check_diskvector_eltype(T) - dv = DiskVector{T}("", Vector{T}(), 0, 0) - finalizer(_finalize_diskvector!, dv) - return dv -end - -function _finalize_diskvector!(dv::DiskVector) - @async _cleanup_diskvector_resources!(dv.mem, dv.path) - return nothing -end - -function _cleanup_diskvector_resources!(mem::Vector, path::String) - if !isempty(path) - @tryonly finalize(mem) - @tryonly rm(path; force=true) - end - return nothing -end - -function _ensure_diskvector_file!(dv::DiskVector) - if isempty(dv.path) - path, io = mktemp(_ark_session_dir()) - close(io) - dv.path = path - end - return dv.path -end - -function _mmap_diskvector(::Type{T}, path::String, capacity::Int) where {T} - return open(path, "r+") do io - Mmap.mmap(io, Vector{T}, capacity, 0; grow=true, shared=true) - end -end - -function _diskvector_uses_disk(dv::DiskVector, requested::Int) - return !isempty(dv.path) || requested > DISKVECTOR_MEMORY_LENGTH -end - -function _ensure_diskvector_memory_capacity!(dv::DiskVector{T}, requested::Int) where {T} - requested <= dv.capacity && return nothing - new_capacity = min(max(requested, 2 * dv.capacity, 1), DISKVECTOR_MEMORY_LENGTH) - new_mem = Vector{T}(undef, new_capacity) - if dv.len > 0 - copyto!(new_mem, 1, dv.mem, 1, dv.len) - end - dv.mem = new_mem - dv.capacity = new_capacity - return nothing -end - -function _move_diskvector_to_disk!(dv::DiskVector{T}, requested::Int) where {T} - old_path = dv.path - path = _ensure_diskvector_file!(dv) - new_capacity = max(requested, 2 * dv.capacity, DISKVECTOR_MEMORY_LENGTH + 1) - old_mem = dv.mem - new_mem = _mmap_diskvector(T, path, new_capacity) - if dv.len > 0 - copyto!(new_mem, 1, old_mem, 1, dv.len) - end - dv.mem = new_mem - dv.capacity = new_capacity - return nothing -end - -function _ensure_diskvector_capacity!(dv::DiskVector{T}, requested::Int) where {T} - requested <= dv.capacity && return nothing - - if !_diskvector_uses_disk(dv, requested) - _ensure_diskvector_memory_capacity!(dv, requested) - return nothing - elseif isempty(dv.path) - _move_diskvector_to_disk!(dv, requested) - return nothing - end - - old_mem = dv.mem - old_capacity = dv.capacity - finalize(old_mem) - new_capacity = max(requested, 2 * old_capacity, 1) - dv.mem = _mmap_diskvector(T, dv.path, new_capacity) - dv.capacity = new_capacity - return nothing -end - -Base.size(dv::DiskVector) = (dv.len,) -Base.length(dv::DiskVector) = dv.len -Base.eltype(::Type{<:DiskVector{T}}) where {T} = T -Base.IndexStyle(::Type{<:DiskVector}) = IndexLinear() - -Base.@propagate_inbounds function Base.getindex(dv::DiskVector, i::Int) - @boundscheck checkbounds(dv, i) - return @inbounds dv.mem[i] -end - -Base.@propagate_inbounds function Base.setindex!(dv::DiskVector, value, i::Int) - @boundscheck checkbounds(dv, i) - @inbounds dv.mem[i] = value - return value -end - -function Base.resize!(dv::DiskVector, new_len::Int) - new_len < 0 && throw(ArgumentError("new length must be ≥ 0")) - _ensure_diskvector_capacity!(dv, new_len) - dv.len = new_len - return dv -end - -function Base.sizehint!(dv::DiskVector, capacity::Int) - if capacity > 0 - _ensure_diskvector_capacity!(dv, capacity) - end - return dv -end - -function Base.empty!(dv::DiskVector) - dv.len = 0 - return dv -end - -function Base.push!(dv::DiskVector, value) - new_len = dv.len + 1 - _ensure_diskvector_capacity!(dv, new_len) - @inbounds dv.mem[new_len] = value - dv.len = new_len - return dv -end - -function Base.pop!(dv::DiskVector) - dv.len == 0 && throw(ArgumentError("array must be non-empty")) - value = @inbounds dv.mem[dv.len] - dv.len -= 1 - return value -end - -function Base.fill!(dv::DiskVector, value) - @inbounds @simd for i in 1:length(dv) - dv.mem[i] = value - end - return dv -end - -function Base.copyto!(dest::DiskVector, doffs::Integer, src::DiskVector, soffs::Integer, n::Integer) - copyto!(dest.mem, doffs, src.mem, soffs, n) - return dest -end - -function Base.unsafe_copyto!( - dest::DiskVector, - doffs::Integer, - src::DiskVector, - soffs::Integer, - n::Integer, -) - unsafe_copyto!(dest.mem, doffs, src.mem, soffs, n) - return dest -end - -function Base.similar(::DiskVector, ::Type{T}, dims::Dims{1}) where {T} - dv = DiskVector{T}() - resize!(dv, dims[1]) - return dv -end diff --git a/src/query.jl b/src/query.jl index 1cf6016a8..533097306 100644 --- a/src/query.jl +++ b/src/query.jl @@ -677,7 +677,6 @@ end :(_gpuvector_view($col_sym, 1:($col_sym).len)) elseif storage_array_types[i] <: StructArray || storage_array_types[i] <: GPUStructArray || - storage_array_types[i] <: DiskStructArray || fieldcount(comp_types[i]) == 0 :(view($col_sym, :)) else @@ -732,8 +731,6 @@ Base.IteratorSize(::Type{<:Query}) = Base.HasLength() :(_StructArrayView_type($T, UnitRange{Int})) elseif storage_type <: GPUStructArray :(_GPUStructArrayView_type($storage_type, UnitRange{Int})) - elseif storage_type <: DiskStructArray - :(_DiskStructArrayView_type($T, UnitRange{Int})) else :(_FieldsViewable_type($storage_type)) end diff --git a/src/storage.jl b/src/storage.jl index 6c4d6ff9f..8cb977774 100644 --- a/src/storage.jl +++ b/src/storage.jl @@ -14,10 +14,6 @@ function _new_storage(::Type{Storage{GPUStructArray{B}}}, ::Type{C}) where {B,C} GPUStructArray{B}(C) end -function _new_storage(::Type{Storage{DiskStructArray}}, ::Type{C}) where {C} - DiskStructArray(C) -end - function _storage_type(::Type{<:Storage{T}}, ::Type{C}) where {T,C} T{C} end @@ -30,10 +26,6 @@ function _storage_type(::Type{Storage{GPUStructArray{B}}}, ::Type{C}) where {B,C _GPUStructArray_type(C, Val{B}()) end -function _storage_type(::Type{Storage{DiskStructArray}}, ::Type{C}) where {C} - _DiskStructArray_type(C) -end - function _storage_type(::Type{Storage{GPUVector{B}}}, ::Type{C}) where {B,C} GPUVector{B,C,_gpuvector_type(C, Val{B}())} end @@ -106,8 +98,6 @@ end return :(GPUStructArray{$QB}(C)) elseif A <: StructArray return :(StructArray(C)) - elseif A <: DiskStructArray - return :(DiskStructArray(C)) else return :(A()) end diff --git a/src/world.jl b/src/world.jl index d2108e1f9..73d53c632 100644 --- a/src/world.jl +++ b/src/world.jl @@ -956,24 +956,18 @@ end ), ) end - if (mode <: Storage{StructArray} || mode <: Storage{<:GPUStructArray} || mode <: Storage{DiskStructArray}) && + if (mode <: Storage{StructArray} || mode <: Storage{<:GPUStructArray}) && fieldcount(T) == 0 throw( ArgumentError("can't use $(mode) for $(nameof(T)) because it has no fields"), ) end - if mode <: Storage{DiskVector} - _check_diskvector_eltype(T) - end - if mode <: Storage{DiskStructArray} - _check_diskstructarray_type(T) - end end # Immutability checks for (T, mode) in zip(types, storage_val_types) if ismutabletype(T) - if mode <: Storage{StructArray} || mode <: Storage{<:GPUStructArray} || mode <: Storage{DiskStructArray} + if mode <: Storage{StructArray} || mode <: Storage{<:GPUStructArray} throw( ArgumentError("Component type $(nameof(T)) must be immutable because it uses StructArray storage"), ) diff --git a/test/Project.toml b/test/Project.toml index 3d6bbe665..af0436cbd 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -3,7 +3,6 @@ Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Ark = "56664e29-41e4-4ea5-ab0e-825499acc647" FieldViews = "ff5a1669-b1f2-423e-bbd7-b7fa0f7e0224" FunctionWrappers = "069b7b12-0de2-55c6-9aab-29f3d0a68a2e" -Mmap = "a63ad114-7e13-5084-954f-fe012c677804" KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Preferences = "21216c6a-2e73-6563-6e65-726566657250" diff --git a/test/include_internals.jl b/test/include_internals.jl index 80279ae54..c5f1f2b28 100644 --- a/test/include_internals.jl +++ b/test/include_internals.jl @@ -21,7 +21,6 @@ using Ark: _has_observers using Ark: StructArray, _StructArray_type, StructArrayView, _new_storage, _storage_type using Ark: GPUStructArray, _GPUStructArray_type, _AbstractStructArray using Ark: _GPUStructArrayView_type, _gpuvectorview_type -using Ark: DiskStructArray, _DiskStructArray_type, _DiskStructArrayView_type using Ark: GPUVectorView, _gpuvector_type, _gpuvector_hostwrap, _gpuvector_view using Ark: Adapt using Ark: _format_type, _state, _storage diff --git a/test/runtests.jl b/test/runtests.jl index 047fba2d1..4fd00503f 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -50,7 +50,6 @@ const MODE_AGNOSTIC_SUITES = [ "test_registry.jl", "test_vec_map.jl", "test_linear_map.jl", - "test_disk_vector.jl", "test_quality.jl", ] diff --git a/test/setup_large.jl b/test/setup_large.jl index 643a4e04f..5cfa540df 100644 --- a/test/setup_large.jl +++ b/test/setup_large.jl @@ -45,12 +45,6 @@ function TestWorld( break end end - for i in 1:length(storages) - if storages[i] == Storage{WrappedVector} && isbitstype(types[i]) && fieldcount(types[i]) > 0 - storages[i] = Storage{DiskVector} - break - end - end for i in 1:length(storages) if storages[i] == Storage{StructArray} storages[i] = Storage{GPUStructArray{:CPU}} @@ -58,8 +52,8 @@ function TestWorld( end storages = Tuple(storages) Ark._World_from_types( - Val{Tuple{fake_types[1:offset_ID]...,types...,fake_types[offset_ID+1:N_fake]...}}(), - Val{Tuple{fake_storage[1:offset_ID]...,storages...,fake_storage[offset_ID+1:N_fake]...}}(), + Val{Tuple{fake_types[1:offset_ID]...,types...,fake_types[(offset_ID+1):N_fake]...}}(), + Val{Tuple{fake_storage[1:offset_ID]...,storages...,fake_storage[(offset_ID+1):N_fake]...}}(), Val{Tuple{relation_types...}}(), Val(allow_mutable), Val(boxed), diff --git a/test/test_disk_vector.jl b/test/test_disk_vector.jl deleted file mode 100644 index 6d957cb88..000000000 --- a/test/test_disk_vector.jl +++ /dev/null @@ -1,178 +0,0 @@ - -function _disk_snapshot(world) - values = Dict{Entity,Position}() - for (entities, positions) in Query(world, (Position,)) - for i in eachindex(entities) - values[entities[i]] = positions[i] - end - end - return values -end - -@testset "DiskVector interface" begin - dv = DiskVector{Int}() - - @test length(dv) == 0 - @test isempty(getfield(dv, :path)) - - sizehint!(dv, 4) - @test length(dv) == 0 - @test isempty(getfield(dv, :path)) - - sizehint!(dv, 1024) - - push!(dv, 1) - push!(dv, 2) - resize!(dv, 4) - dv[3] = 3 - dv[4] = 4 - @test collect(dv) == [1, 2, 3, 4] - - fill!(view(dv, 2:3), 9) - @test collect(dv) == [1, 9, 9, 4] - - @test pop!(dv) == 4 - @test collect(dv) == [1, 9, 9] - - dv2 = DiskVector{Int}() - resize!(dv2, length(dv)) - copyto!(dv2, 1, dv, 1, length(dv)) - @test collect(dv2) == collect(dv) - - dv3 = DiskVector{Int}() - resize!(dv3, length(dv)) - unsafe_copyto!(dv3, 1, dv, 1, length(dv)) - @test collect(dv3) == collect(dv) - - dv4 = similar(dv, Int, (2,)) - @test dv4 isa DiskVector{Int} - @test length(dv4) == 2 - - empty!(dv) - @test isempty(dv) - - @test_throws( - "ArgumentError: DiskVector storage requires an isbits component type, got NoIsBits", - DiskVector{NoIsBits}() - ) - @test_throws( - "ArgumentError: DiskVector storage requires a nonzero-size component type, got LabelComponent", - DiskVector{LabelComponent}() - ) -end - -@testset "tryonly catch" begin - dir = mktempdir() - write(joinpath(dir, "keep.txt"), "x") - Ark._cleanup_diskvector_resources!(Int[], dir) - @test ispath(joinpath(dir, "keep.txt")) -end - -@testset "DiskVector validation" begin - @test_throws( - "ArgumentError: DiskVector storage requires an isbits component type, got NoIsBits", - World(NoIsBits => Storage{DiskVector}) - ) - @test_throws( - "ArgumentError: DiskVector storage requires an isbits component type, got MutableComponent", - World(MutableComponent => Storage{DiskVector}; allow_mutable=true) - ) - @test_throws( - "ArgumentError: DiskVector storage requires a nonzero-size component type, got LabelComponent", - World(LabelComponent => Storage{DiskVector}) - ) -end - -@testset "DiskVector components" begin - world = World( - A => Storage{DiskVector}, - B => Storage{DiskVector}, - Relation{DiskRelation} => Storage{DiskVector}; - initial_capacity=1024, - ) - - e1 = new_entity!(world, (A(2.0), B(2.0))) - @test get_components(world, e1, (A, B)) == (A(2.0), B(2.0)) - - e2 = new_entity!(world, (A(2.0), B(2.0), DiskRelation(1) => e1)) - @test get_components(world, e2, (A, B, DiskRelation)) == (A(2.0), B(2.0), DiskRelation(1)) - @test get_relations(world, e2, (DiskRelation,)) == (e1,) - - a, b, rel = get_components(world, e2, (A, B, DiskRelation)) - set_components!(world, e2, (A(a.x + 1.0), B(b.x + 1.0), rel)) - @test get_components(world, e2, (A, B)) == (A(3.0), B(3.0)) - - remove_components!(world, e2, (A, DiskRelation)) - @test has_components(world, e2, (A, DiskRelation)) == false - @test get_components(world, e2, (B,)) == (B(3.0),) - - add_components!(world, e2, (A(4.0), DiskRelation(2) => e1)) - @test has_components(world, e2, (A, DiskRelation)) == true - @test get_components(world, e2, (A, DiskRelation)) == (A(4.0), DiskRelation(2)) - @test get_relations(world, e2, (DiskRelation,)) == (e1,) - - e3 = copy_entity!(world, e2) - @test get_components(world, e3, (A, B, DiskRelation)) == (A(4.0), B(3.0), DiskRelation(2)) - - remove_entity!(world, e2) - @test is_alive(world, e2) == false - @test is_alive(world, e1) == true - - reset!(world) - @test isempty(collect(Query(world, (A,)))) -end - -@testset "DiskVector query and batch operations" begin - world = World( - Position => Storage{DiskVector}, - Velocity => Storage{DiskVector}, - Health => Storage{DiskVector}, - Int64 => Storage{DiskVector}; - initial_capacity=1024, - ) - - new_entities!(world, 10, (Position, Velocity, Health, Int64)) do (entities, positions, velocities, healths, ints) - @test positions isa FieldViewable - @test velocities isa FieldViewable - @test healths isa FieldViewable - @test ints isa SubArray - for i in eachindex(entities) - positions[i] = Position(i, i) - velocities[i] = Velocity(2i, 3i) - healths[i] = Health(i) - ints[i] = i - end - end - - for columns in Query(world, (Position, Velocity, Int64)) - @unpack _, (x, y), (dx, dy), ints = columns - @test x isa FieldView - @test y isa FieldView - @test dx isa FieldView - @test dy isa FieldView - @test ints isa SubArray - for i in eachindex(x) - x[i] += dx[i] - y[i] += dy[i] - ints[i] += 1 - end - end - - for (_, positions, ints) in Query(world, (Position, Int64)) - for i in eachindex(positions) - @test positions[i] == Position(3i, 4i) - @test ints[i] == i + 1 - end - end - - before = _disk_snapshot(world) - filter = Filter(world, (Position, Health)) - shuffle_entities!(world, filter) - @test _disk_snapshot(world) == before - - sort_entities!(world, filter) - @test _disk_snapshot(world) == before - - partition_entities!(world, filter; pred=e -> isodd(e._id)) - @test _disk_snapshot(world) == before -end diff --git a/test/test_structarray.jl b/test/test_structarray.jl index 62e0585c4..7370a99ba 100644 --- a/test/test_structarray.jl +++ b/test/test_structarray.jl @@ -319,158 +319,3 @@ end remove_entities!(w, Filter(w, (A, B))) reset!(w) end - -@testset "DiskStructArray basic functionality" begin - a = DiskStructArray(Position) - - @test isa(a.x, DiskVector{Float64}) - @test isa(a.y, DiskVector{Float64}) - - push!(a, Position(1, 2)) - - @test length(a) == 1 - @test a[1] == Position(1, 2) - - a[1] = Position(3, 4) - @test a[1] == Position(3, 4) - - push!(a, Position(5, 6)) - @test length(a) == 2 - - pop!(a) - @test length(a) == 1 - - fill!(a, Position(99, 99)) - for pos in a - @test pos == Position(99, 99) - end -end - -@testset "DiskStructArray type" begin - tp = _DiskStructArray_type(Position) - @test tp == DiskStructArray{Position,@NamedTuple{x::DiskVector{Float64}, y::DiskVector{Float64}},2} -end - -@testset "DiskStructArray invalid types" begin - @test_throws( - "for LabelComponent because it has no fields", - World(LabelComponent => Storage{DiskStructArray}) - ) - @test_throws( - "DiskStructArray storage not allowed for components without fields", - DiskStructArray(LabelComponent) - ) - @test_throws( - "DiskVector storage requires an isbits component type, got Array", - World(NoIsBits => Storage{DiskStructArray}) - ) - @test_throws( - "must be immutable because it uses StructArray storage", - World(MutableComponent => Storage{DiskStructArray}; allow_mutable=true) - ) -end - -@testset "DiskStructArray view" begin - a = DiskStructArray(Position) - for i in 1:4 - push!(a, Position(i, 10i)) - end - - v = view(a, 2:3) - # The declared column types must match the ones actually constructed, or the - # StructArrayView constructor cannot convert them. - @test typeof(v) == _DiskStructArrayView_type(Position, UnitRange{Int}) - @test v isa StructArrayView - @test length(v) == 2 - @test v[1] == Position(2, 20) - - x, y = unpack(v) - @test x isa SubArray{Float64,1,DiskVector{Float64}} - @test x == [2, 3] - @test y == [20, 30] - - v[1] = Position(5, 50) - # The view aliases the array it was taken from. - @test a[2] == Position(5, 50) - - x .+= 1 - @test a[2] == Position(6, 50) - - full = view(a, :) - @test length(full) == length(a) - @test full[1] == a[1] - - fill!(v, Position(0, 0)) - @test a[2] == Position(0, 0) && a[3] == Position(0, 0) - @test a[1] == Position(1, 10) -end - -@testset "DiskStructArray query columns" begin - w = World(A => Storage{DiskStructArray}) - for i in 1:3 - new_entity!(w, (A(i),)) - end - - for (entities, as) in Query(w, (A,)) - @test as isa StructArrayView - xs, = unpack(as) - @test xs isa SubArray{Float64,1,DiskVector{Float64}} - xs .*= 2 - end - - @test sort([a.x for (_, as) in Query(w, (A,)) for a in as]) == [2.0, 4.0, 6.0] - reset!(w) -end - -@testset "DiskStructArray components" begin - w = World( - A => Storage{DiskStructArray}, - B => Storage{DiskStructArray}, - ) - e1 = new_entity!(w, (A(0.0), B(0.0))) - @test get_components(w, e1, (A, B)) == (A(0.0), B(0.0)) - e2 = new_entity!(w, (A(0.0), B(0.0))) - @test get_components(w, e2, (A, B)) == (A(0.0), B(0.0)) - e3 = copy_entity!(w, e1) - @test e1 != e2 && e2 != e3 - - a, b = get_components(w, e2, (A, B)) - set_components!(w, e2, (A(a.x + 1.0), B(b.x + 1.0))) - @test get_components(w, e2, (A, B)) == (A(1.0), B(1.0)) - remove_components!(w, e2, (A,)) - @test get_components(w, e2, (B,)) == (B(1.0),) - @test has_components(w, e2, (A,)) == false - add_components!(w, e2, (A(0.0),)) - @test has_components(w, e2, (A,)) == true - - remove_entity!(w, e2) - @test is_alive(w, e1) == true - @test is_alive(w, e2) == false - - new_entities!(w, 2, (A(0.0), B(0.0))) - @test isempty(collect(Query(w, (A, B)))) == false - remove_entities!(w, Filter(w, (A, B))) - reset!(w) -end - -@testset "DiskStructArray spills to disk" begin - w = World(Position => Storage{DiskStructArray}) - n = 4 * Ark.DISKVECTOR_MEMORY_LENGTH - for i in 1:n - new_entity!(w, (Position(i, 2i),)) - end - - for (entities, positions) in Query(w, (Position,)) - x, y = unpack(positions) - x .+= y - end - - total = 0.0 - for (entities, positions) in Query(w, (Position,)) - for p in positions - total += p.x - end - end - @test total == sum(3i for i in 1:n) - reset!(w) -end diff --git a/test/test_world.jl b/test/test_world.jl index 3c3a0454b..f5b4984ab 100644 --- a/test/test_world.jl +++ b/test/test_world.jl @@ -558,7 +558,6 @@ end ) using FunctionWrappers - using Mmap excluded = Set([ FunctionWrappers.gen_fptr, Base.unsafe_convert, @@ -566,8 +565,7 @@ end ]) function_filter(@nospecialize f) = !(f in excluded) - rep = JET.@report_opt function_filter = function_filter ignored_modules = - (AnyFrameModule(Mmap), AnyFrameModule(Base.Filesystem)) new_entity!(world, (Position(1, 2), Velocity(3, 4))) + rep = JET.@report_opt function_filter = function_filter new_entity!(world, (Position(1, 2), Velocity(3, 4))) reports = filter(!is_known_false_positive, JET.get_reports(rep)) isempty(reports) || println(reports) @test isempty(reports) @@ -1174,7 +1172,6 @@ end Velocity => Storage{StructArray}, ) using FunctionWrappers - using Mmap excluded = Set([ FunctionWrappers.gen_fptr, Base.unsafe_convert, @@ -1183,8 +1180,7 @@ end function_filter(@nospecialize f) = !(f in excluded) #@test_opt function_filter = function_filter new_entities!(world, 100, (Position, Velocity)) - rep = JET.@report_opt function_filter = function_filter ignored_modules = - (AnyFrameModule(Mmap), AnyFrameModule(Base.Filesystem)) new_entities!( + rep = JET.@report_opt function_filter = function_filter new_entities!( world, 100, (Position(13, 13), Velocity(13, 13)), @@ -1426,7 +1422,6 @@ end Velocity => Storage{StructArray}, ) using FunctionWrappers - using Mmap excluded = Set([ FunctionWrappers.gen_fptr, Base.unsafe_convert, @@ -1435,8 +1430,7 @@ end function_filter(@nospecialize f) = !(f in excluded) e1 = new_entity!(world, ()) - rep = JET.@report_opt function_filter = function_filter ignored_modules = - (AnyFrameModule(Mmap), AnyFrameModule(Base.Filesystem)) add_components!( + rep = JET.@report_opt function_filter = function_filter add_components!( world, e1, (Position(1, 2), Velocity(3, 4)),