Optimized a lot

This commit is contained in:
Christian Treffs 2017-10-30 09:02:03 +01:00
parent a33281b1fa
commit 7c7b38253b
10 changed files with 399 additions and 85 deletions

View File

@ -9,11 +9,15 @@ let package = Package(
// Products define the executables and libraries produced by a package, and make them visible to other packages. // Products define the executables and libraries produced by a package, and make them visible to other packages.
.library( .library(
name: "FirebladeECS", name: "FirebladeECS",
targets: ["FirebladeECS"]) targets: ["FirebladeECS"]),
.executable(
name: "FirebladeECSDemo",
targets: ["FirebladeECSDemo"])
], ],
dependencies: [ dependencies: [
// Dependencies declare other packages that this package depends on. // Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"), // .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/PureSwift/CSDL2.git", .branch("master"))
], ],
targets: [ targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets are the basic building blocks of a package. A target can define a module or a test suite.
@ -21,6 +25,9 @@ let package = Package(
.target( .target(
name: "FirebladeECS", name: "FirebladeECS",
dependencies: []), dependencies: []),
.target(
name: "FirebladeECSDemo",
dependencies: ["FirebladeECS"]),
.testTarget( .testTarget(
name: "FirebladeECSTests", name: "FirebladeECSTests",
dependencies: ["FirebladeECS"]) dependencies: ["FirebladeECS"])

View File

@ -1,62 +0,0 @@
//
// ContiguousComponentArray.swift
// FirebladeECS
//
// Created by Christian Treffs on 28.10.17.
//
private let pow2: [Int] = [ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648, 4294967296
]
private func nearestToPow2(_ value: Int) -> Int {
let exp = (value.bitWidth-value.leadingZeroBitCount)
return pow2[exp]
}
public class ContiguousComponentArray {
public typealias Element = Component
private var _store: ContiguousArray<Element?>
public init(minEntityCount minCount: Int) {
let count = nearestToPow2(minCount)
_store = ContiguousArray<Element?>(repeating: nil, count: count)
}
public func insert(_ element: Element, at entityIdx: EntityIndex) {
if needsToGrow(entityIdx) {
grow(to: entityIdx)
}
_store[entityIdx] = element
}
public func has(_ entityIdx: EntityIndex) -> Bool {
if _store.count <= entityIdx { return false }
return _store[entityIdx] != nil
}
public func get(at entityIdx: EntityIndex) -> Element? {
return _store[entityIdx]
}
public func remove(at entityIdx: EntityIndex) {
return _store[entityIdx] = nil
}
fileprivate func needsToGrow(_ entityId: EntityIndex) -> Bool {
return entityId > _store.count - 1
}
fileprivate func grow(to minIndex: Int) {
if minIndex >= _store.count {
let newCapacity: Int = nearestToPow2(minIndex)
let count: Int = newCapacity-_store.count
let nilElements: ContiguousArray<Element?> = ContiguousArray<Element?>.init(repeating: nil, count: count)
_store.reserveCapacity(newCapacity)
_store.append(contentsOf: nilElements)
}
}
}

View File

@ -43,7 +43,7 @@ extension Family {
/*public var members: LazyMapCollection<LazyFilterCollection<LazyMapCollection<EntityIdSet, Entity?>>, Entity> { /*public var members: LazyMapCollection<LazyFilterCollection<LazyMapCollection<EntityIdSet, Entity?>>, Entity> {
return nexus.members(of: self) return nexus.members(of: self)
}*/ }*/
internal var memberIds: EntityIdSet { internal var memberIds: [EntityIdentifier] {
return nexus.members(of: self) return nexus.members(of: self)
} }
} }

View File

@ -0,0 +1,119 @@
//
// ManagedContiguousArray.swift
// FirebladeECS
//
// Created by Christian Treffs on 28.10.17.
//
private let pow2: [Int] = [ 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608, 16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648, 4294967296
]
private func nearestToPow2(_ value: Int) -> Int {
let exp = (value.bitWidth-value.leadingZeroBitCount)
return pow2[exp]
}
public protocol ManagedContiguousArrayProtocol: class {
associatedtype Element
static var chunkSize: Int { get }
init(minCount: Int)
func insert(_ element: Element, at index: Int)
func has(_ index: Int) -> Bool
func get(at index: Int) -> Element?
func remove(at index: Int)
}
public class ManagedContiguousArray: ManagedContiguousArrayProtocol {
public static var chunkSize: Int = 4096
public typealias Element = Any
var _store: ContiguousArray<Element?> = []
public required init(minCount: Int = chunkSize) {
_store = ContiguousArray<Element?>(repeating: nil, count: minCount)
}
public func insert(_ element: Element, at index: Int) {
if needsToGrow(index) {
grow(including: index)
}
_store[index] = element
}
public func has(_ index: Int) -> Bool {
if _store.count <= index { return false }
return _store[index] != nil
}
public func get(at index: Int) -> Element? {
return _store[index]
}
public func remove(at index: Int) {
return _store[index] = nil
}
internal func needsToGrow(_ index: Int) -> Bool {
return index > _store.count - 1
}
internal func grow(including index: Int) {
//var t = Timer()
//t.start()
let newCapacity: Int = nearest(to: index)
let count: Int = newCapacity-_store.count
//_store.reserveCapacity(newCapacity)
for _ in 0..<count {
_store.append(nil)
}
//t.stop()
//print("did grow to \(newCapacity) in \(t.milliSeconds)ms")
}
internal func nearest(to index: Int) -> Int {
let delta = Float(index) / Float(ManagedContiguousArray.chunkSize)
let multiplier = Int(delta) + 1
return multiplier * ManagedContiguousArray.chunkSize
}
}
public class ContiguousComponentArray: ManagedContiguousArray {
public typealias Element = Component
}
public class ContiguousEntityIdArray: ManagedContiguousArray {
public typealias Element = EntityIdentifier
}
/*
public func insert(_ element: Element, at entityIdx: EntityIndex) {
super.insert(element, at: entityIdx)
}
public func has(_ entityIdx: EntityIndex) -> Bool {
if _store.count <= entityIdx { return false }
return _store[entityIdx] != nil
}
public func get(at entityIdx: EntityIndex) -> Element? {
return _store[entityIdx]
}
public func remove(at entityIdx: EntityIndex) {
return _store[entityIdx] = nil
}
fileprivate func needsToGrow(_ entityId: EntityIndex) -> Bool {
return entityId > _store.count - 1
}
fileprivate func grow(to minIndex: Int) {
let newCapacity: Int = nearestToPow2(minIndex)
let count: Int = newCapacity-_store.count
let nilElements: ContiguousArray<Element?> = ContiguousArray<Element?>.init(repeating: nil, count: count)
_store.reserveCapacity(newCapacity)
_store.append(contentsOf: nilElements)
}
*/

View File

@ -34,7 +34,7 @@ extension Nexus {
if componentsByType[componentId] != nil { if componentsByType[componentId] != nil {
componentsByType[componentId]!.insert(component, at: entityIdx) componentsByType[componentId]!.insert(component, at: entityIdx)
} else { } else {
componentsByType[componentId] = UniformComponents(minEntityCount: entities.count) componentsByType[componentId] = UniformComponents()
componentsByType[componentId]!.insert(component, at: entityIdx) componentsByType[componentId]!.insert(component, at: entityIdx)
} }
@ -63,7 +63,7 @@ extension Nexus {
public func get(component componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> Component? { public func get(component componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> Component? {
guard let uniformComponents: UniformComponents = componentsByType[componentId] else { return nil } guard let uniformComponents: UniformComponents = componentsByType[componentId] else { return nil }
return uniformComponents.get(at: entityId.index) return uniformComponents.get(at: entityId.index) as? Component
} }
public func get<C>(for entityId: EntityIdentifier) -> C? where C: Component { public func get<C>(for entityId: EntityIdentifier) -> C? where C: Component {

View File

@ -39,23 +39,24 @@ extension Nexus {
return family.traits.isMatch(components: componentSet) return family.traits.isMatch(components: componentSet)
} }
public func members(of family: Family) -> EntityIdSet { public func members(of family: Family) -> [EntityIdentifier] {
let traitHash: FamilyTraitSetHash = family.traits.hashValue let traitHash: FamilyTraitSetHash = family.traits.hashValue
return familyMembersByTraitHash[traitHash] ?? [] // FIXME: fail? return familyMembersByTraitHash[traitHash] ?? [] // FIXME: fail?
} }
/*public func members(of family: Family) -> LazyMapCollection<LazyFilterCollection<LazyMapCollection<EntityIdSet, Entity?>>, Entity> {
return members(of: family).lazy.flatMap { self.get(entity: $0) }
}*/
public func isMember(_ entity: Entity, in family: Family) -> Bool { public func isMember(_ entity: Entity, in family: Family) -> Bool {
return isMember(entity.identifier, in: family) return isMember(entity.identifier, in: family)
} }
public func isMember(byHash traitSetEntityIdHash: TraitEntityIdHash) -> Bool {
return familyContainsEntityId[traitSetEntityIdHash] ?? false
}
public func isMember(_ entityId: EntityIdentifier, in family: Family) -> Bool { public func isMember(_ entityId: EntityIdentifier, in family: Family) -> Bool {
let traitHash: FamilyTraitSetHash = family.traits.hashValue let traitHash: FamilyTraitSetHash = family.traits.hashValue
// FIXME: this may be costly for many entities in family // FIXME: this is costly!
return familyMembersByTraitHash[traitHash]?.contains(entityId) ?? false guard let members: [EntityIdentifier] = familyMembersByTraitHash[traitHash] else { return false }
return members.contains(entityId)
} }
fileprivate func get(family traits: FamilyTraitSet) -> Family? { fileprivate func get(family traits: FamilyTraitSet) -> Family? {
@ -82,44 +83,57 @@ extension Nexus {
// MARK: - update family membership // MARK: - update family membership
fileprivate func calculateTraitEntityIdHash(traitHash: FamilyTraitSetHash, entityIdx: EntityIndex) -> TraitEntityIdHash {
return hash(combine: traitHash, entityIdx)
}
func update(membership family: Family, for entityId: EntityIdentifier) { func update(membership family: Family, for entityId: EntityIdentifier) {
let entityIdx: EntityIndex = entityId.index let entityIdx: EntityIndex = entityId.index
let traitHash: FamilyTraitSetHash = family.traits.hashValue
guard let componentIds: ComponentIdentifiers = componentIdsByEntity[entityIdx] else { return } guard let componentIds: ComponentIdentifiers = componentIdsByEntity[entityIdx] else { return }
// FIXME: bottle neck
let trash: TraitEntityIdHash = calculateTraitEntityIdHash(traitHash: traitHash, entityIdx: entityIdx)
let is_Member: Bool = isMember(byHash: trash)
let componentsSet: ComponentSet = ComponentSet.init(componentIds) let componentsSet: ComponentSet = ComponentSet.init(componentIds)
let isMember: Bool = family.isMember(entityId)
let isMatch: Bool = family.traits.isMatch(components: componentsSet) let isMatch: Bool = family.traits.isMatch(components: componentsSet)
switch (isMatch, isMember) { switch (isMatch, is_Member) {
case (true, false): case (true, false):
add(to: family, entityId: entityId) add(to: family, entityId: entityId, with: trash)
case (false, true): case (false, true):
remove(from: family, entityId: entityId) remove(from: family, entityId: entityId, with: trash)
default: default:
break break
} }
} }
fileprivate func add(to family: Family, entityId: EntityIdentifier) { fileprivate func add(to family: Family, entityId: EntityIdentifier, with traitEntityIdHash: TraitEntityIdHash) {
let traitHash: FamilyTraitSetHash = family.traits.hashValue let traitHash: FamilyTraitSetHash = family.traits.hashValue
if familyMembersByTraitHash[traitHash] != nil { if familyMembersByTraitHash[traitHash] != nil {
familyMembersByTraitHash[traitHash]?.insert(entityId) // here we already checked if entity is a member
familyMembersByTraitHash[traitHash]!.append(entityId)
} else { } else {
familyMembersByTraitHash[traitHash] = EntityIdSet(arrayLiteral: entityId) familyMembersByTraitHash[traitHash] = [EntityIdentifier].init(arrayLiteral: entityId)
familyMembersByTraitHash.reserveCapacity(4096)
} }
familyContainsEntityId[traitEntityIdHash] = true
notify(FamilyMemberAdded(member: entityId, to: family.traits)) notify(FamilyMemberAdded(member: entityId, to: family.traits))
} }
fileprivate func remove(from family: Family, entityId: EntityIdentifier) { fileprivate func remove(from family: Family, entityId: EntityIdentifier, with traitEntityIdHash: TraitEntityIdHash) {
let traitHash: FamilyTraitSetHash = family.traits.hashValue let traitHash: FamilyTraitSetHash = family.traits.hashValue
guard let removed: EntityIdentifier = familyMembersByTraitHash[traitHash]?.remove(entityId) else { // FIXME: index of is not cheep
guard let indexInFamily = familyMembersByTraitHash[traitHash]?.index(of: entityId) else {
assert(false, "removing entity id \(entityId) that is not in family \(family)") assert(false, "removing entity id \(entityId) that is not in family \(family)")
report("removing entity id \(entityId) that is not in family \(family)") report("removing entity id \(entityId) that is not in family \(family)")
return return
} }
let removed: EntityIdentifier = familyMembersByTraitHash[traitHash]!.remove(at: indexInFamily)
familyContainsEntityId[traitEntityIdHash] = false
notify(FamilyMemberRemoved(member: removed, from: family.traits)) notify(FamilyMemberRemoved(member: removed, from: family.traits))
} }

View File

@ -42,7 +42,8 @@ public class Nexus {
var freeEntities: ContiguousArray<EntityIdentifier> var freeEntities: ContiguousArray<EntityIdentifier>
var familiyByTraitHash: [FamilyTraitSetHash: Family] var familiyByTraitHash: [FamilyTraitSetHash: Family]
var familyMembersByTraitHash: [FamilyTraitSetHash: EntityIdSet] var familyMembersByTraitHash: [FamilyTraitSetHash: [EntityIdentifier]]
var familyContainsEntityId: [TraitEntityIdHash: Bool]
public init() { public init() {
entities = Entities() entities = Entities()
@ -52,6 +53,7 @@ public class Nexus {
freeEntities = ContiguousArray<EntityIdentifier>() freeEntities = ContiguousArray<EntityIdentifier>()
familiyByTraitHash = [:] familiyByTraitHash = [:]
familyMembersByTraitHash = [:] familyMembersByTraitHash = [:]
familyContainsEntityId = [:]
} }
} }

View File

@ -0,0 +1,10 @@
//
// Profiler.swift
// FirebladeECS
//
// Created by Christian Treffs on 28.10.17.
//
struct Profiler {
}

View File

@ -0,0 +1,50 @@
//
// Timer.swift
// FirebladeECS
//
// Created by Christian Treffs on 28.10.17.
//
import Darwin.Mach.mach_time
struct Timer {
private let numerator: UInt64
private let denominator: UInt64
private var startTime: UInt64 = 0
private var stopTime: UInt64 = 0
init() {
var timeBaseInfo = mach_timebase_info.init(numer: 0, denom: 0 )
let success: kern_return_t = mach_timebase_info(&timeBaseInfo)
assert(KERN_SUCCESS == success)
numerator = UInt64(timeBaseInfo.numer)
denominator = UInt64(timeBaseInfo.denom)
}
mutating func start() {
startTime = mach_absolute_time()
}
mutating func stop() {
stopTime = mach_absolute_time()
}
mutating func reset() {
startTime = 0
stopTime = 0
}
var nanoSeconds: UInt64 {
return ((stopTime - startTime) * numerator) / denominator
}
var microSeconds: Double {
return Double(nanoSeconds) / 1.0e3
}
var milliSeconds: Double {
return Double(nanoSeconds) / 1.0e6
}
var seconds: Double {
return Double(nanoSeconds) / 1.0e9
}
}

View File

@ -0,0 +1,174 @@
import CSDL2
import FirebladeECS
if SDL_Init(SDL_INIT_VIDEO) != 0 {
fatalError("could not init video")
}
let width: Int32 = 640
let height: Int32 = 480
let hWin = SDL_CreateWindow("Fireblade ECS demo", 100, 100, width, height, SDL_WINDOW_SHOWN.rawValue)
if hWin == nil {
SDL_Quit()
fatalError("could not crate window")
}
func randNorm() -> Double {
return Double(arc4random()) / Double(UInt32.max)
}
// won't produce pure black
func randColor() -> UInt8 {
return UInt8(randNorm() * 254) + 1
}
let nexus = Nexus()
class Position: Component {
var x: Int32 = width/2
var y: Int32 = height/2
}
class Color: Component {
var r: UInt8 = randColor()
var g: UInt8 = randColor()
var b: UInt8 = randColor()
}
func createScene() {
let numEntities: Int = 10_000
for i in 0..<numEntities {
let e = nexus.create(entity: "\(i)")
e.assign(Position())
e.assign(Color())
}
}
class PositionSystem {
let family = nexus.family(requiresAll: [Position.self], excludesAll: [])
var acceleration: Double = 4.0
func update() {
family.iterate(components: Position.self) { [unowned self](_, pos) in
let deltaX: Double = self.acceleration*((randNorm() * 2) - 1)
let deltaY: Double = self.acceleration*((randNorm() * 2) - 1)
var x = pos!.x + Int32(deltaX)
var y = pos!.y + Int32(deltaY)
if x < 0 || x > width {
x = -x
}
if y < 0 || y > height {
y = -y
}
pos!.x = x
pos!.y = y
}
}
}
class PositionResetSystem {
let family = nexus.family(requiresAll: [Position.self], excludesAll: [])
func update() {
family.iterate(components: Position.self) { (_, pos) in
pos!.x = width/2
pos!.y = height/2
}
}
}
class ColorSystem {
let family = nexus.family(requiresAll: [Color.self], excludesAll: [])
func update() {
family.iterate(components: Color.self) { (_, color) in
color!.r = randColor()
color!.g = randColor()
color!.b = randColor()
}
}
}
class RenderSystem {
let hRenderer: OpaquePointer?
let family = nexus.family(requiresAll: [Position.self, Color.self], excludesAll: [])
init(hWin: OpaquePointer?) {
hRenderer = SDL_CreateRenderer(hWin, -1, SDL_RENDERER_ACCELERATED.rawValue)
if hRenderer == nil {
SDL_DestroyWindow(hWin)
SDL_Quit()
fatalError("could not create renderer")
}
}
deinit {
SDL_DestroyRenderer(hRenderer)
}
func render() {
SDL_SetRenderDrawColor( hRenderer, 0, 0, 0, 255 ) // black
SDL_RenderClear(hRenderer) // clear screen
family.iterate(components: Position.self, Color.self) { [unowned self] (_, pos, color) in
var rect = SDL_Rect(x: pos!.x, y: pos!.y, w: 2, h: 2)
SDL_SetRenderDrawColor(self.hRenderer, color!.r, color!.g, color!.b, 255)
SDL_RenderFillRect(self.hRenderer, &rect)
}
SDL_RenderPresent(hRenderer)
}
}
let positionSystem = PositionSystem()
let positionResetSystem = PositionResetSystem()
let renderSystem = RenderSystem(hWin: hWin)
let colorSystem = ColorSystem()
createScene()
var event: SDL_Event = SDL_Event()
var quit: Bool = false
while quit == false {
while SDL_PollEvent(&event) == 1 {
switch SDL_EventType(rawValue: event.type) {
case SDL_QUIT:
quit = true
break
case SDL_KEYDOWN:
switch Int(event.key.keysym.sym) {
case SDLK_ESCAPE:
quit = true
break
case SDLK_c:
colorSystem.update()
case SDLK_r:
positionResetSystem.update()
case SDLK_s:
positionSystem.acceleration = 0.0
case SDLK_PLUS:
positionSystem.acceleration += 0.1
case SDLK_MINUS:
positionSystem.acceleration -= 0.1
case SDLK_SPACE:
positionSystem.acceleration = 4.0
default:
break
}
default:
break
}
}
positionSystem.update()
renderSystem.render()
}
SDL_DestroyWindow(hWin)
SDL_Quit()