Optimized a lot
This commit is contained in:
parent
a33281b1fa
commit
7c7b38253b
|
|
@ -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"])
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = [:]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
//
|
||||||
|
// Profiler.swift
|
||||||
|
// FirebladeECS
|
||||||
|
//
|
||||||
|
// Created by Christian Treffs on 28.10.17.
|
||||||
|
//
|
||||||
|
|
||||||
|
struct Profiler {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
Loading…
Reference in New Issue