Skip to content

Commit

Permalink
Merge pull request cs3217-2324#107 from zheng-ze/rework-entitymanager
Browse files Browse the repository at this point in the history
Optimise Renderer and EntityManager
  • Loading branch information
Vanessamae23 authored Apr 11, 2024
2 parents 18326b4 + 8408d96 commit fd1cf7a
Show file tree
Hide file tree
Showing 26 changed files with 401 additions and 171 deletions.
12 changes: 8 additions & 4 deletions TowerForge/TowerForge.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
3CD37AA52BBEC10700222D8A /* FirebaseRemoteEventSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CD37AA42BBEC10700222D8A /* FirebaseRemoteEventSubscriber.swift */; };
3CD37AA72BBEC5EF00222D8A /* BaseRemoteEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CD37AA62BBEC5EF00222D8A /* BaseRemoteEvent.swift */; };
3CE9514B2BAC83FA008B2785 /* SpawnableEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE9514A2BAC83FA008B2785 /* SpawnableEntities.swift */; };
3CE9514F2BAC8936008B2785 /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE9514E2BAC8936008B2785 /* Renderer.swift */; };
3CE9514F2BAC8936008B2785 /* TFRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE9514E2BAC8936008B2785 /* TFRenderer.swift */; };
3CE951512BAC8955008B2785 /* Renderable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951502BAC8955008B2785 /* Renderable.swift */; };
3CE951562BACA0CF008B2785 /* Collidable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951552BACA0CF008B2785 /* Collidable.swift */; };
3CE951582BAD724D008B2785 /* TFContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951572BAD724D008B2785 /* TFContact.swift */; };
Expand All @@ -71,6 +71,7 @@
3CE951652BAE0A04008B2785 /* HomeSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951642BAE0A04008B2785 /* HomeSystem.swift */; };
3CE951672BAEAB0E008B2785 /* ContactSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951662BAEAB0E008B2785 /* ContactSystem.swift */; };
3CE951692BAEB719008B2785 /* RequestSpawnEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951682BAEB719008B2785 /* RequestSpawnEvent.swift */; };
3CF273242BC6E51F007E645C /* Renderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF273232BC6E51F007E645C /* Renderer.swift */; };
3CFA72E72BC0398E0081337F /* RemoteEventManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CFA72E62BC0398E0081337F /* RemoteEventManager.swift */; };
5200624E2BA8D597000DBA30 /* AiComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5200624D2BA8D597000DBA30 /* AiComponent.swift */; };
520062562BA8E026000DBA30 /* PlayerSpawnable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520062552BA8E026000DBA30 /* PlayerSpawnable.swift */; };
Expand Down Expand Up @@ -282,7 +283,7 @@
3CD37AA42BBEC10700222D8A /* FirebaseRemoteEventSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseRemoteEventSubscriber.swift; sourceTree = "<group>"; };
3CD37AA62BBEC5EF00222D8A /* BaseRemoteEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseRemoteEvent.swift; sourceTree = "<group>"; };
3CE9514A2BAC83FA008B2785 /* SpawnableEntities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpawnableEntities.swift; sourceTree = "<group>"; };
3CE9514E2BAC8936008B2785 /* Renderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = "<group>"; };
3CE9514E2BAC8936008B2785 /* TFRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFRenderer.swift; sourceTree = "<group>"; };
3CE951502BAC8955008B2785 /* Renderable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderable.swift; sourceTree = "<group>"; };
3CE951552BACA0CF008B2785 /* Collidable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collidable.swift; sourceTree = "<group>"; };
3CE951572BAD724D008B2785 /* TFContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFContact.swift; sourceTree = "<group>"; };
Expand All @@ -292,6 +293,7 @@
3CE951642BAE0A04008B2785 /* HomeSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeSystem.swift; sourceTree = "<group>"; };
3CE951662BAEAB0E008B2785 /* ContactSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSystem.swift; sourceTree = "<group>"; };
3CE951682BAEB719008B2785 /* RequestSpawnEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSpawnEvent.swift; sourceTree = "<group>"; };
3CF273232BC6E51F007E645C /* Renderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Renderer.swift; sourceTree = "<group>"; };
3CFA72E62BC0398E0081337F /* RemoteEventManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteEventManager.swift; sourceTree = "<group>"; };
5200624D2BA8D597000DBA30 /* AiComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AiComponent.swift; sourceTree = "<group>"; };
520062552BA8E026000DBA30 /* PlayerSpawnable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSpawnable.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -550,7 +552,8 @@
isa = PBXGroup;
children = (
3CAC4A632BB696D700A5D22E /* RenderStages */,
3CE9514E2BAC8936008B2785 /* Renderer.swift */,
3CE9514E2BAC8936008B2785 /* TFRenderer.swift */,
3CF273232BC6E51F007E645C /* Renderer.swift */,
3CAC4A662BB6975200A5D22E /* RenderStage.swift */,
3CE951502BAC8955008B2785 /* Renderable.swift */,
);
Expand Down Expand Up @@ -1392,6 +1395,7 @@
3CE951632BAE037C008B2785 /* AiSystem.swift in Sources */,
3CE9515F2BADE2C5008B2785 /* ShootingSystem.swift in Sources */,
5240D0AB2BB3340F004F1486 /* CaptureTheFlagMode.swift in Sources */,
3CF273242BC6E51F007E645C /* Renderer.swift in Sources */,
3CD37A9D2BBEBD1600222D8A /* RemoteSpawnable.swift in Sources */,
3C9955CA2BA5888F00D33FA5 /* SpawnEvent.swift in Sources */,
3CE951582BAD724D008B2785 /* TFContact.swift in Sources */,
Expand Down Expand Up @@ -1459,7 +1463,7 @@
527A07862BB411FB00CD9D08 /* GameOverScene.swift in Sources */,
3C9955B12BA4ACA100D33FA5 /* Bullet.swift in Sources */,
3CA829C62BB719A500D8E72A /* ButtonRenderStage.swift in Sources */,
3CE9514F2BAC8936008B2785 /* Renderer.swift in Sources */,
3CE9514F2BAC8936008B2785 /* TFRenderer.swift in Sources */,
527A07802BB3F81C00CD9D08 /* TimerComponent.swift in Sources */,
BAFFB9662BB98ADC00D8301F /* AchievementStorage.swift in Sources */,
3C769A722BA58DE700F454F9 /* MovementSystem.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableAddressSanitizer = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ class SpriteComponent: TFComponent {
var animatableKey: String
var alpha = 1.0
var staticOnScreen = false
var zPosition: CGFloat

init(textureNames: [String], size: CGSize, animatableKey: String) {
init(textureNames: [String], size: CGSize, animatableKey: String, zPosition: CGFloat = .zero) {
self.textures = TFTextures(textureNames: textureNames, textureAtlasName: "Sprites")
self.size = size
self.animatableKey = animatableKey
self.zPosition = zPosition
super.init()
}
}
14 changes: 14 additions & 0 deletions TowerForge/TowerForge/GameModule/Components/TFComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@

import Foundation

struct TFComponentTypeWrapper {
let type: TFComponent.Type
}

extension TFComponentTypeWrapper: Hashable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.type == rhs.type
}

func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(type))
}
}

class TFComponent: Identifiable {
var id = UUID()
weak var entity: TFEntity?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation

class BaseProjectile: TFEntity, Spawnable {
static let zPosition: CGFloat = 0
required init(position: CGPoint, player: Player) {
// TODO: to fill
}
Expand All @@ -16,7 +17,8 @@ class BaseProjectile: TFEntity, Spawnable {
player: Player, velocity: CGVector = .zero) {
super.init()
// Core Components
self.addComponent(SpriteComponent(textureNames: textureNames, size: size, animatableKey: key))
self.addComponent(SpriteComponent(textureNames: textureNames, size: size,
animatableKey: key, zPosition: BaseProjectile.zPosition))
self.addComponent(PositionComponent(position: position))
self.addComponent(MovableComponent(velocity: velocity))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation

class BaseTower: TFEntity {
static let zPosition: CGFloat = 200
init(textureNames: [String],
size: CGSize,
key: String,
Expand All @@ -17,7 +18,8 @@ class BaseTower: TFEntity {
id: UUID = UUID()) {
super.init()
// Core Components
self.addComponent(SpriteComponent(textureNames: textureNames, size: size, animatableKey: key))
self.addComponent(SpriteComponent(textureNames: textureNames, size: size,
animatableKey: key, zPosition: BaseTower.zPosition))
self.addComponent(PositionComponent(position: position))

// Game Components
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation

class BaseUnit: TFEntity {
static let zPosition: CGFloat = 100
init(textureNames: [String],
size: CGSize,
key: String,
Expand All @@ -18,7 +19,8 @@ class BaseUnit: TFEntity {
id: UUID = UUID()) {
super.init(id: id)
// Core Components
self.addComponent(SpriteComponent(textureNames: textureNames, size: size, animatableKey: key))
self.addComponent(SpriteComponent(textureNames: textureNames, size: size,
animatableKey: key, zPosition: BaseUnit.zPosition))
self.addComponent(PositionComponent(position: position))
self.addComponent(MovableComponent(velocity: velocity))

Expand Down
29 changes: 26 additions & 3 deletions TowerForge/TowerForge/GameModule/Entities/EntityManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import Foundation

class EntityManager {
private var entitiesMap: [UUID: TFEntity]
private var componentsMap: [TFComponentTypeWrapper: [UUID: TFEntity]]

init() {
entitiesMap = Dictionary()
entitiesMap = [:]
componentsMap = [:]
}

var entities: [TFEntity] {
Expand All @@ -24,14 +26,34 @@ class EntityManager {

func add(_ entity: TFEntity) {
entitiesMap[entity.id] = entity
for (key, _) in entity.components {
if componentsMap[key] == nil {
componentsMap[key] = [:]
}
componentsMap[key]?[entity.id] = entity
}
/// assert(checkRepresentation())
}

func removeEntity(with id: UUID) {
entitiesMap.removeValue(forKey: id)
guard let entity = entitiesMap.removeValue(forKey: id) else {
return
}

for (key, _) in entity.components {
componentsMap[key]?.removeValue(forKey: entity.id)
}
/// assert(checkRepresentation())
}

func entities<T: TFComponent>(with componentType: T.Type) -> [TFEntity] {
guard let entityMap = componentsMap[TFComponentTypeWrapper(type: componentType)] else {
return []
}

return Array(entityMap.values)
}

func component<T: TFComponent>(ofType type: T.Type, of entityId: UUID) -> T? {
guard let entity = entity(with: entityId) else {
return nil
Expand All @@ -42,7 +64,8 @@ class EntityManager {

func components<T: TFComponent>(ofType type: T.Type) -> [T] {
/// assert(checkRepresentation())
entities.compactMap { $0.component(ofType: type) }
let typeWrapper = TFComponentTypeWrapper(type: type)
return componentsMap[typeWrapper]?.compactMap({ $0.value.component(ofType: type) }) ?? []
}

/// Ensures that the UUID keys of entries in the dictionary match the UUID id of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class ArrowTower: BaseTower, PlayerSpawnable {
static let textureNames = ["LightHouse-1"]
static let size = CGSize(width: 100, height: 100)
static let key = "arrowTower"
static let maxHealth = 200.0
static let maxHealth = 500.0
static let damage = 10.0
static var cost = 10
static let range = 400.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class MeleeUnit: BaseUnit, PlayerSpawnable {
static let textureNames = ["melee-1", "melee-2"]
static let size = CGSize(width: 100, height: 100)
static let key = "melee"
static let maxHealth = 100.0
static let damage = 10.0
static let maxHealth = 150.0
static let damage = 15.0
static var cost = 10
static let attackRate = 1.0
static let velocity = CGVector(dx: 30.0, dy: 0.0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ class WizardUnit: BaseUnit, PlayerSpawnable {
static let textureNames = ["Wizard-0", "Wizard-1", "Wizard-2"]
static let size = CGSize(width: 100, height: 100)
static let key = "shoot"
static let maxHealth = 100.0
static let maxHealth = 80.0
static let damage = 10.0
static var cost = 5
static var cost = 10
static let attackRate = 1.0
static let velocity = CGVector(dx: 50.0, dy: 0.0)
static let range = 400.0
Expand Down
21 changes: 9 additions & 12 deletions TowerForge/TowerForge/GameModule/Entities/TFEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,16 @@ import Foundation

class TFEntity: Collidable {
let id: UUID
private(set) var components: [UUID: TFComponent]
private(set) var components: [TFComponentTypeWrapper: TFComponent]

init(id: UUID = UUID()) {
self.id = id
components = Dictionary()
}

func component<T: TFComponent>(ofType type: T.Type) -> T? {
for component in components.values {
guard let component = component as? T else {
continue
}
return component
}
return nil
let typeWrapper = TFComponentTypeWrapper(type: type)
return components[typeWrapper] as? T
}

func hasComponent<T: TFComponent>(ofType type: T.Type) -> Bool {
Expand All @@ -36,7 +31,8 @@ class TFEntity: Collidable {
return
}
component.didAddToEntity(self)
components[component.id] = (component)
let typeWrapper = TFComponentTypeWrapper(type: type(of: component))
components[typeWrapper] = component
}

func removeComponent<T: TFComponent>(ofType type: T.Type) {
Expand All @@ -45,7 +41,8 @@ class TFEntity: Collidable {
return
}
componentToBeRemoved.willRemoveFromEntity()
components.removeValue(forKey: componentToBeRemoved.id)
let typeWrapper = TFComponentTypeWrapper(type: type)
components.removeValue(forKey: typeWrapper)
}

// To be overriden by sub classes as needed
Expand Down Expand Up @@ -79,8 +76,8 @@ class TFEntity: Collidable {
/// Ensures that the UUID keys of entries in the dictionary match the UUID id of
/// the associated values
private func checkRepresentation() -> Bool {
for (key, _) in components where key != components[key]?.id {
return false
for (key, _) in components where type(of: components[key]) == key.type {
return false
}
return true
}
Expand Down
5 changes: 5 additions & 0 deletions TowerForge/TowerForge/GameModule/GameEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ protocol AbstractGameEngine: EventTarget {
func addRemoteEvent(_ remoteEvent: TFRemoteEvent)

func system<T: TFSystem>(ofType type: T.Type) -> T?
func entities<T: TFComponent>(with componentType: T.Type) -> [TFEntity]
}

/// A class that encapsulates handling of all Managers.
Expand Down Expand Up @@ -71,6 +72,10 @@ class GameEngine: AbstractGameEngine {
systemManager.system(ofType: type)
}

func entities<T: TFComponent>(with componentType: T.Type) -> [TFEntity] {
entityManager.entities(with: componentType)
}

private func setupTeam() {
let ownTeam = Team(player: .ownPlayer)
let oppositeTeam = Team(player: .oppositePlayer)
Expand Down
6 changes: 5 additions & 1 deletion TowerForge/TowerForge/GameModule/GameWorld.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class GameWorld {
powerUpSelectionNode = PowerUpSelectionNode(eventManager: gameEngine.eventManager)
grid = Grid(screenSize: worldBounds)
popup = StatePopupNode()
renderer = Renderer(target: self, scene: scene)
renderer = TFRenderer(target: self, scene: scene)

setUp()
}
Expand Down Expand Up @@ -116,6 +116,10 @@ extension GameWorld: Renderable {
var entitiesToRender: [TFEntity] {
gameEngine.entities
}

func entities<T: TFComponent>(with componentType: T.Type) -> [TFEntity] {
gameEngine.entities(with: componentType)
}
}

extension GameWorld: UnitSelectionNodeDelegate {
Expand Down
13 changes: 13 additions & 0 deletions TowerForge/TowerForge/Scenes/GameScene.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,17 @@ extension GameScene: TFScene {
func panCamera(by displacement: CGVector) {
cameraNode?.move(by: displacement)
}

func isStatic(node: TFNode) -> Bool {
cameraNode?.contains(node) ?? false
}

func setNode(_ node: TFNode, isStatic: Bool) {
guard isStatic != self.isStatic(node: node) else {
return
}

remove(node: node)
add(node: node, staticOnScreen: isStatic)
}
}
23 changes: 17 additions & 6 deletions TowerForge/TowerForge/Scenes/Rendering/RenderStage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,25 @@

import Foundation

protocol RenderTarget: AnyObject {
var target: Renderable { get }
var renderedNodes: [UUID: TFNode] { get }
func flagNodeUpdated(with id: UUID)
func updateStaticNode(with id: UUID)
}

protocol RenderStage {
func createAndAdd(node: TFNode, for entity: TFEntity)
func transform(node: TFNode, for entity: TFEntity)
func update(node: TFNode, for entity: TFEntity)
func render()
func create(for entity: TFEntity)
func transform(for entity: TFEntity)
func update(for entity: TFEntity)
func removeAndUncache(for id: UUID)
}

extension RenderStage {
func createAndAdd(node: TFNode, for entity: TFEntity) {}
func transform(node: TFNode, for entity: TFEntity) {}
func update(node: TFNode, for entity: TFEntity) {}
func render() {}
func create(for entity: TFEntity) {}
func transform(for entity: TFEntity) {}
func update(for entity: TFEntity) {}
func removeAndUncache(for id: UUID) {}
}
Loading

0 comments on commit fd1cf7a

Please sign in to comment.