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.
.library(
name: "FirebladeECS",
targets: ["FirebladeECS"])
targets: ["FirebladeECS"]),
.executable(
name: "FirebladeECSDemo",
targets: ["FirebladeECSDemo"])
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/PureSwift/CSDL2.git", .branch("master"))
],
targets: [
// 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(
name: "FirebladeECS",
dependencies: []),
.target(
name: "FirebladeECSDemo",
dependencies: ["FirebladeECS"]),
.testTarget(
name: "FirebladeECSTests",
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> {
return nexus.members(of: self)
}*/
internal var memberIds: EntityIdSet {
internal var memberIds: [EntityIdentifier] {
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 {
componentsByType[componentId]!.insert(component, at: entityIdx)
} else {
componentsByType[componentId] = UniformComponents(minEntityCount: entities.count)
componentsByType[componentId] = UniformComponents()
componentsByType[componentId]!.insert(component, at: entityIdx)
}
@ -63,7 +63,7 @@ extension Nexus {
public func get(component componentId: ComponentIdentifier, for entityId: EntityIdentifier) -> Component? {
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 {

View File

@ -39,23 +39,24 @@ extension Nexus {
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
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 {
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 {
let traitHash: FamilyTraitSetHash = family.traits.hashValue
// FIXME: this may be costly for many entities in family
return familyMembersByTraitHash[traitHash]?.contains(entityId) ?? false
// FIXME: this is costly!
guard let members: [EntityIdentifier] = familyMembersByTraitHash[traitHash] else { return false }
return members.contains(entityId)
}
fileprivate func get(family traits: FamilyTraitSet) -> Family? {
@ -82,44 +83,57 @@ extension Nexus {
// 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) {
let entityIdx: EntityIndex = entityId.index
let traitHash: FamilyTraitSetHash = family.traits.hashValue
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 isMember: Bool = family.isMember(entityId)
let isMatch: Bool = family.traits.isMatch(components: componentsSet)
switch (isMatch, isMember) {
switch (isMatch, is_Member) {
case (true, false):
add(to: family, entityId: entityId)
add(to: family, entityId: entityId, with: trash)
case (false, true):
remove(from: family, entityId: entityId)
remove(from: family, entityId: entityId, with: trash)
default:
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
if familyMembersByTraitHash[traitHash] != nil {
familyMembersByTraitHash[traitHash]?.insert(entityId)
// here we already checked if entity is a member
familyMembersByTraitHash[traitHash]!.append(entityId)
} 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))
}
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
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)")
report("removing entity id \(entityId) that is not in family \(family)")
return
}
let removed: EntityIdentifier = familyMembersByTraitHash[traitHash]!.remove(at: indexInFamily)
familyContainsEntityId[traitEntityIdHash] = false
notify(FamilyMemberRemoved(member: removed, from: family.traits))
}

View File

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