Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions MiniBlocks.playground/Sources/Actor/WorldActor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// The actor on which the world's model state lives.
@globalActor
actor WorldActor {
static let shared = WorldActor()
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ class AchievementHUDLoadComponent: GKComponent {
entity?.component(ofType: SpriteNodeComponent.self)?.node
}

private var world: World? {
@WorldActor private var world: World? {
get { entity?.component(ofType: WorldAssociationComponent.self)?.world }
set { entity?.component(ofType: WorldAssociationComponent.self)?.world = newValue }
}

private var playerInfo: PlayerInfo? {
@WorldActor private var playerInfo: PlayerInfo? {
get { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo }
set { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo = newValue }
}

private var achievements: Achievements? {
@WorldActor private var achievements: Achievements? {
playerInfo?.achievements
}

private var nextAchievement: Achievements? {
@WorldActor private var nextAchievement: Achievements? {
achievements?.next
}

Expand All @@ -41,15 +41,21 @@ class AchievementHUDLoadComponent: GKComponent {
}

override func update(deltaTime seconds: TimeInterval) {
Task.detached { @WorldActor in
await self._update(deltaTime: seconds)
}
}

@WorldActor private func _update(deltaTime seconds: TimeInterval) async {
guard let node = node else { return }

if lastAchievement != nextAchievement {
// Update when achievements change
node.removeAllChildren()
await node.removeAllChildren()

if let nextAchievement = nextAchievement,
let achievementNode = makeAchievementHUDNode(for: nextAchievement, fontSize: 13) {
node.addChild(achievementNode)
await node.addChild(achievementNode)
}

lastAchievement = nextAchievement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ class DebugHUDLoadComponent: GKComponent {
entity?.component(ofType: SpriteNodeComponent.self)?.node as? SKLabelNode
}

private var world: World? {
@WorldActor private var world: World? {
get { entity?.component(ofType: WorldAssociationComponent.self)?.world }
set { entity?.component(ofType: WorldAssociationComponent.self)?.world = newValue }
}

private var playerInfo: PlayerInfo? {
@WorldActor private var playerInfo: PlayerInfo? {
get { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo }
set { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo = newValue }
}
Expand All @@ -21,9 +21,17 @@ class DebugHUDLoadComponent: GKComponent {
}

override func update(deltaTime seconds: TimeInterval) {
Task.detached { @WorldActor in
await self._update(deltaTime: seconds)
}
}

@WorldActor private func _update(deltaTime seconds: TimeInterval) async {
guard let playerInfo = playerInfo,
let node = node else { return }

let text: String?

if playerInfo.hasDebugHUDEnabled {
var stats = [
("Position", format(pos: playerInfo.position)),
Expand All @@ -38,10 +46,14 @@ class DebugHUDLoadComponent: GKComponent {
]
}

node.text = stats.map { "\($0.0): \($0.1)" }.joined(separator: "\n")
text = stats.map { "\($0.0): \($0.1)" }.joined(separator: "\n")
} else {
node.text = nil
text = nil
}

await Task { @MainActor in
node.text = text
}.value
}

private func format(pos: BlockPos3) -> String {
Expand Down
12 changes: 9 additions & 3 deletions MiniBlocks.playground/Sources/Component/HandLoadComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,27 @@ class HandLoadComponent: GKComponent {
entity?.component(ofType: HandNodeComponent.self)?.node
}

private var world: World? {
@WorldActor private var world: World? {
get { entity?.component(ofType: WorldAssociationComponent.self)?.world }
set { entity?.component(ofType: WorldAssociationComponent.self)?.world = newValue }
}

private var playerInfo: PlayerInfo? {
@WorldActor private var playerInfo: PlayerInfo? {
get { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo }
set { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo = newValue }
}

private var item: Item? {
@WorldActor private var item: Item? {
playerInfo?.selectedHotbarStack?.item
}

override func update(deltaTime seconds: TimeInterval) {
Task.detached { @WorldActor in
self._update(deltaTime: seconds)
}
}

@WorldActor private func _update(deltaTime seconds: TimeInterval) {
guard let node = node,
lastItem != item else { return }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,57 +15,65 @@ class HotbarHUDLoadComponent: GKComponent {
entity?.component(ofType: SpriteNodeComponent.self)?.node
}

private var world: World? {
@WorldActor private var world: World? {
get { entity?.component(ofType: WorldAssociationComponent.self)?.world }
set { entity?.component(ofType: WorldAssociationComponent.self)?.world = newValue }
}

private var playerInfo: PlayerInfo? {
@WorldActor private var playerInfo: PlayerInfo? {
get { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo }
set { entity?.component(ofType: PlayerAssociationComponent.self)?.playerInfo = newValue }
}

private var inventory: Inventory? {
@WorldActor private var inventory: Inventory? {
get { playerInfo?.hotbar }
set { playerInfo?.hotbar = newValue! }
}

override func update(deltaTime seconds: TimeInterval) {
Task.detached { @WorldActor in
await self._update(deltaTime: seconds)
}
}

@WorldActor private func _update(deltaTime seconds: TimeInterval) async {
guard let node = node else { return }

if inventory != lastInventory {
// Redraw slots as inventory has changed
// TODO: Delta updates?
node.removeAllChildren()
await node.removeAllChildren()

if let inventory = inventory {
let slotSize: CGFloat = 40
let itemSize: CGFloat = slotSize * 0.8
let width = CGFloat(inventory.slotCount) * slotSize

for i in 0..<inventory.slotCount {
let lineThickness = slotLineThickness(for: i)
let slotNode = makeHotbarHUDSlotNode(size: slotSize, lineThickness: lineThickness)
slotNode.position = CGPoint(x: (CGFloat(i) * slotSize) - (width / 2) + (slotSize / 2), y: slotSize / 2)
if let stack = inventory[i] {
// TODO: Render stack count
slotNode.addChild(makeItemNode(for: stack.item, size: itemSize))
await Task { @MainActor in
for i in 0..<inventory.slotCount {
let lineThickness = await slotLineThickness(for: i)
let slotNode = makeHotbarHUDSlotNode(size: slotSize, lineThickness: lineThickness)
slotNode.position = CGPoint(x: (CGFloat(i) * slotSize) - (width / 2) + (slotSize / 2), y: slotSize / 2)
if let stack = inventory[i] {
// TODO: Render stack count
slotNode.addChild(makeItemNode(for: stack.item, size: itemSize))
}
node.addChild(slotNode)
}
node.addChild(slotNode)
}
}.value
}
} else if playerInfo?.selectedHotbarSlot != lastSelectedHotbarSlot, let inventory = inventory {
// Update only outlines as selection has changed
for i in 0..<inventory.slotCount {
let lineThickness = slotLineThickness(for: i)
updateHotbarHUDSlotNode(node.children[i], lineThickness: lineThickness)
let lineThickness = await slotLineThickness(for: i)
updateHotbarHUDSlotNode(await node.children[i], lineThickness: lineThickness)
}
}

lastInventory = inventory
}

private func slotLineThickness(for i: Int) -> CGFloat {
playerInfo?.selectedHotbarSlot == i ? selectedSlotLineThickness : normalSlotLineThickness
private func slotLineThickness(for i: Int) async -> CGFloat {
await playerInfo?.selectedHotbarSlot == i ? selectedSlotLineThickness : normalSlotLineThickness
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ class PlayerAssociationComponent: GKComponent {
entity?.component(ofType: WorldAssociationComponent.self)
}

private var world: World? {
@WorldActor private var world: World? {
get { worldAssocationComponent?.world }
set { worldAssocationComponent?.world = newValue! }
}

var playerInfo: PlayerInfo? {
@WorldActor var playerInfo: PlayerInfo? {
get { playerName.flatMap { world?[playerInfoFor: $0] } }
set {
guard let playerName = playerName else { return }
Expand Down
Loading