Skip to content
Merged
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
74 changes: 41 additions & 33 deletions Sources/UntoldEngine/Scenes/SceneSerializer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -652,25 +652,29 @@ public func deserializeScene(sceneData: SceneData, meshLoadingMode: MeshLoadingM
}
case .asyncDefault:
setEntityMeshAsync(entityId: entityId, filename: filename, withExtension: withExtension, assetName: nil) { success in
if success {
Logger.log(message: "✅ Asset instance '\(sceneDataEntity.name)' loaded")
// Apply overrides after async import completes
applyAssetInstanceOverrides(entityId: entityId, overrides: assetInstance.overrides)

// Setup animations (skeleton is now available)
if sceneDataEntity.hasAnimationComponent == true {
for animations in sceneDataEntity.animations {
let animationFilename = animations.deletingPathExtension().lastPathComponent
let animationFilenameExt = animations.pathExtension
setEntityAnimations(entityId: entityId, filename: animationFilename, withExtension: animationFilenameExt, name: animationFilename)
changeAnimation(entityId: entityId, name: animationFilename)
}
if let animationComponent = scene.get(component: AnimationComponent.self, for: entityId) {
animationComponent.animationsFilenames = sceneDataEntity.animations
Task {
await MainActor.run {
if success {
Logger.log(message: "✅ Asset instance '\(sceneDataEntity.name)' loaded")
// Apply overrides after async import completes
applyAssetInstanceOverrides(entityId: entityId, overrides: assetInstance.overrides)

// Setup animations (skeleton is now available)
if sceneDataEntity.hasAnimationComponent == true {
for animations in sceneDataEntity.animations {
let animationFilename = animations.deletingPathExtension().lastPathComponent
let animationFilenameExt = animations.pathExtension
setEntityAnimations(entityId: entityId, filename: animationFilename, withExtension: animationFilenameExt, name: animationFilename)
changeAnimation(entityId: entityId, name: animationFilename)
}
if let animationComponent = scene.get(component: AnimationComponent.self, for: entityId) {
animationComponent.animationsFilenames = sceneDataEntity.animations
}
}
} else {
Logger.logWarning(message: "❌ Asset instance '\(sceneDataEntity.name)' failed to load")
}
}
} else {
Logger.logWarning(message: "❌ Asset instance '\(sceneDataEntity.name)' failed to load")
}
}
}
Expand Down Expand Up @@ -699,24 +703,28 @@ public func deserializeScene(sceneData: SceneData, meshLoadingMode: MeshLoadingM
let fallbackLabel = withExtension.isEmpty ? filename : "\(filename).\(withExtension)"
let meshLabel = sceneDataEntity.name.isEmpty ? fallbackLabel : sceneDataEntity.name
setEntityMeshAsync(entityId: entityId, filename: filename, withExtension: withExtension, assetName: sceneDataEntity.assetName) { success in
applyLocalTransform()
if success {
Logger.log(message: "✅ Mesh loaded for \(meshLabel)")

// Setup animations (skeleton is now available)
if sceneDataEntity.hasAnimationComponent == true {
for animations in sceneDataEntity.animations {
let animationFilename = animations.deletingPathExtension().lastPathComponent
let animationFilenameExt = animations.pathExtension
setEntityAnimations(entityId: entityId, filename: animationFilename, withExtension: animationFilenameExt, name: animationFilename)
changeAnimation(entityId: entityId, name: animationFilename)
}
if let animationComponent = scene.get(component: AnimationComponent.self, for: entityId) {
animationComponent.animationsFilenames = sceneDataEntity.animations
Task {
await MainActor.run {
applyLocalTransform()
if success {
Logger.log(message: "✅ Mesh loaded for \(meshLabel)")

// Setup animations (skeleton is now available)
if sceneDataEntity.hasAnimationComponent == true {
for animations in sceneDataEntity.animations {
let animationFilename = animations.deletingPathExtension().lastPathComponent
let animationFilenameExt = animations.pathExtension
setEntityAnimations(entityId: entityId, filename: animationFilename, withExtension: animationFilenameExt, name: animationFilename)
changeAnimation(entityId: entityId, name: animationFilename)
}
if let animationComponent = scene.get(component: AnimationComponent.self, for: entityId) {
animationComponent.animationsFilenames = sceneDataEntity.animations
}
}
} else {
Logger.logWarning(message: "❌ Mesh failed for \(meshLabel)")
}
}
} else {
Logger.logWarning(message: "❌ Mesh failed for \(meshLabel)")
}
}
}
Expand Down
97 changes: 84 additions & 13 deletions Sources/UntoldEngine/Systems/InputSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,32 @@ import Foundation
import GameController
import simd

public struct GamePadState {
public struct GameControllerState {
public var aPressed = false
public var bPressed = false
public var xPressed = false
public var yPressed = false

public var dpadUpPressed = false
public var dpadDownPressed = false
public var dpadLeftPressed = false
public var dpadRightPressed = false

public var leftShoulderPressed = false
public var rightShoulderPressed = false
public var leftTriggerPressed = false
public var rightTriggerPressed = false
public var leftTriggerValue: Float = 0
public var rightTriggerValue: Float = 0

public var leftThumbstickX: Float = 0
public var leftThumbstickY: Float = 0
public var rightThumbstickX: Float = 0
public var rightThumbstickY: Float = 0
public var leftThumbStickActive = false
public var rightThumbStickActive = false
public var leftThumbstickPressed = false
public var rightThumbstickPressed = false
}

public enum PanGestureState { case began, changed, ended }
Expand All @@ -37,8 +59,8 @@ public final class InputSystem {
public let kVK_ANSI_Space: UInt16 = 49, kVK_ANSI_J: UInt16 = 38, kVK_ANSI_K: UInt16 = 40

public var keyState = KeyState()
public var gamePadState = GamePadState()
public var currentGamepad: GCExtendedGamepad?
public var gameControllerState = GameControllerState()
public var currentGameController: GCExtendedGamepad?

// Shared state
public var currentPanGestureState: PanGestureState?
Expand All @@ -55,9 +77,9 @@ public final class InputSystem {
public var pinchDelta: simd_float3 = .init(0, 0, 0)
public var previousScale: CGFloat = 1

init() { setupGameController() }
init() { registerGameControllerEvents() }

private func setupGameController() {
public func registerGameControllerEvents() {
NotificationCenter.default.addObserver(self,
selector: #selector(controllerDidConnect(_:)),
name: .GCControllerDidConnect, object: nil)
Expand All @@ -70,19 +92,68 @@ public final class InputSystem {
}

@objc private func controllerDidConnect(_ note: Notification) {
guard let controller = note.object as? GCController, let gamepad = controller.extendedGamepad else { return }
currentGamepad = gamepad
configureGamepadHandlers(gamepad)
guard let controller = note.object as? GCController, let gameController = controller.extendedGamepad else { return }
currentGameController = gameController
configureGameControllerHandlers(gameController)
Logger.log(message: "Game Controller \(controller.vendorName ?? "unknown vendor") connected and Configured")
}

@objc private func controllerDidDisconnect(_ note: Notification) {
guard let controller = note.object as? GCController else { return }
if currentGamepad === controller.extendedGamepad { currentGamepad = nil }
if currentGameController === controller.extendedGamepad { currentGameController = nil }
Logger.log(message: "Game Controller \(controller.vendorName ?? "unknown vendor") disconnected")
}

private func configureGamepadHandlers(_ gamepad: GCExtendedGamepad) {
gamepad.buttonA.pressedChangedHandler = { [weak self] _, _, pressed in self?.gamePadState.aPressed = pressed }
gamepad.buttonB.pressedChangedHandler = { [weak self] _, _, pressed in self?.gamePadState.bPressed = pressed }
// add thumbstick/trigger mapping as needed…
private func configureGameControllerHandlers(_ gameController: GCExtendedGamepad) {
gameController.buttonA.pressedChangedHandler = { [weak self] _, _, pressed in self?.gameControllerState.aPressed = pressed }
gameController.buttonB.pressedChangedHandler = { [weak self] _, _, pressed in self?.gameControllerState.bPressed = pressed }
gameController.buttonX.pressedChangedHandler = { [weak self] _, _, pressed in self?.gameControllerState.xPressed = pressed }
gameController.buttonY.pressedChangedHandler = { [weak self] _, _, pressed in self?.gameControllerState.yPressed = pressed }

gameController.dpad.valueChangedHandler = { [weak self] _, xValue, yValue in
guard let self else { return }
gameControllerState.dpadUpPressed = yValue > 0.5
gameControllerState.dpadDownPressed = yValue < -0.5
gameControllerState.dpadLeftPressed = xValue < -0.5
gameControllerState.dpadRightPressed = xValue > 0.5
}

gameController.leftShoulder.pressedChangedHandler = { [weak self] _, _, pressed in
self?.gameControllerState.leftShoulderPressed = pressed
}
gameController.rightShoulder.pressedChangedHandler = { [weak self] _, _, pressed in
self?.gameControllerState.rightShoulderPressed = pressed
}

gameController.leftTrigger.valueChangedHandler = { [weak self] _, value, pressed in
guard let self else { return }
gameControllerState.leftTriggerValue = value
gameControllerState.leftTriggerPressed = pressed
}
gameController.rightTrigger.valueChangedHandler = { [weak self] _, value, pressed in
guard let self else { return }
gameControllerState.rightTriggerValue = value
gameControllerState.rightTriggerPressed = pressed
}

gameController.leftThumbstick.valueChangedHandler = { [weak self] _, xValue, yValue in
guard let self else { return }
gameControllerState.leftThumbstickX = xValue
gameControllerState.leftThumbstickY = yValue
gameControllerState.leftThumbStickActive = abs(xValue) > 0.1 || abs(yValue) > 0.1
}
gameController.rightThumbstick.valueChangedHandler = { [weak self] _, xValue, yValue in
guard let self else { return }
gameControllerState.rightThumbstickX = xValue
gameControllerState.rightThumbstickY = yValue
gameControllerState.rightThumbStickActive = abs(xValue) > 0.1 || abs(yValue) > 0.1
}

gameController.leftThumbstickButton?.pressedChangedHandler = { [weak self] _, _, pressed in
self?.gameControllerState.leftThumbstickPressed = pressed
}
gameController.rightThumbstickButton?.pressedChangedHandler = { [weak self] _, _, pressed in
self?.gameControllerState.rightThumbstickPressed = pressed
}
}
}
69 changes: 40 additions & 29 deletions Sources/UntoldEngine/Systems/RegistrationSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,15 +227,19 @@ public func setEntityMeshAsync(
handleError(.filenameNotFound, filename)
await loadFallbackMesh(entityId: entityId, filename: filename)
await AssetLoadingState.shared.finishLoading(entityId: entityId)
completion?(false)
await MainActor.run {
completion?(false)
}
return
}

if url.pathExtension == "dae" {
handleError(.fileTypeNotSupported, url.pathExtension)
await loadFallbackMesh(entityId: entityId, filename: filename)
await AssetLoadingState.shared.finishLoading(entityId: entityId)
completion?(false)
await MainActor.run {
completion?(false)
}
return
}

Expand All @@ -256,7 +260,9 @@ public func setEntityMeshAsync(
handleError(.assetDataMissing, filename)
await loadFallbackMesh(entityId: entityId, filename: filename)
await AssetLoadingState.shared.finishLoading(entityId: entityId)
completion?(false)
await MainActor.run {
completion?(false)
}
return
}

Expand All @@ -269,7 +275,9 @@ public func setEntityMeshAsync(
handleError(.assetDataMissing, "No mesh with asset name \(assetNameExist)")
await loadFallbackMesh(entityId: entityId, filename: filename)
await AssetLoadingState.shared.finishLoading(entityId: entityId)
completion?(false)
await MainActor.run {
completion?(false)
}
return
}
}
Expand Down Expand Up @@ -375,7 +383,9 @@ public func setEntityMeshAsync(
}

await AssetLoadingState.shared.finishLoading(entityId: entityId)
completion?(true)
await MainActor.run {
completion?(true)
}
}
}

Expand Down Expand Up @@ -486,14 +496,18 @@ public func loadSceneAsync(
guard let url = LoadingSystem.shared.resourceURL(forResource: filename, withExtension: withExtension, subResource: nil) else {
handleError(.filenameNotFound, filename)
await AssetLoadingState.shared.finishLoading(entityId: sceneLoadEntityId)
completion?(false)
await MainActor.run {
completion?(false)
}
return
}

if url.pathExtension == "dae" {
handleError(.fileTypeNotSupported, url.pathExtension)
await AssetLoadingState.shared.finishLoading(entityId: sceneLoadEntityId)
completion?(false)
await MainActor.run {
completion?(false)
}
return
}

Expand All @@ -510,39 +524,36 @@ public func loadSceneAsync(
}

// Process on main thread
var didLoadMeshes = true
await MainActor.run {
if meshes.isEmpty {
handleError(.assetDataMissing, filename)
Task {
await AssetLoadingState.shared.finishLoading(entityId: sceneLoadEntityId)
}
completion?(false)
didLoadMeshes = false
return
}

for mesh in meshes {
if mesh.count > 0 {
let entityId = createEntity()
for mesh in meshes where mesh.count > 0 {
let entityId = createEntity()

if hasComponent(entityId: entityId, componentType: LocalTransformComponent.self) == false {
registerTransformComponent(entityId: entityId)
}

if hasComponent(entityId: entityId, componentType: ScenegraphComponent.self) == false {
registerSceneGraphComponent(entityId: entityId)
}
if hasComponent(entityId: entityId, componentType: LocalTransformComponent.self) == false {
registerTransformComponent(entityId: entityId)
}

associateMeshesToEntity(entityId: entityId, meshes: mesh)
registerRenderComponent(entityId: entityId, meshes: mesh, url: url, assetName: mesh.first!.assetName)
setEntityName(entityId: entityId, name: mesh.first!.assetName)
setEntitySkeleton(entityId: entityId, filename: filename, withExtension: withExtension)
if hasComponent(entityId: entityId, componentType: ScenegraphComponent.self) == false {
registerSceneGraphComponent(entityId: entityId)
}
}

Task {
await AssetLoadingState.shared.finishLoading(entityId: sceneLoadEntityId)
associateMeshesToEntity(entityId: entityId, meshes: mesh)
registerRenderComponent(entityId: entityId, meshes: mesh, url: url, assetName: mesh.first!.assetName)
setEntityName(entityId: entityId, name: mesh.first!.assetName)
setEntitySkeleton(entityId: entityId, filename: filename, withExtension: withExtension)
}
completion?(true)
}

await AssetLoadingState.shared.finishLoading(entityId: sceneLoadEntityId)

await MainActor.run {
completion?(didLoadMeshes)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions Tests/UntoldEngineTests/InputSystemExtensionsTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ final class InputSystemExtensionsTests: XCTestCase {
input.delegate = nil

input.keyState = KeyState()
input.gamePadState = GamePadState()
input.currentGamepad = nil
input.gameControllerState = GameControllerState()
input.currentGameController = nil

input.currentPanGestureState = nil
input.currentPinchGestureState = nil
Expand Down
6 changes: 3 additions & 3 deletions Tests/UntoldEngineTests/InputSystemTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ final class InputSystemTests: XCTestCase {
input.delegate = nil

input.keyState = KeyState()
input.gamePadState = GamePadState()
input.currentGamepad = nil
input.gameControllerState = GameControllerState()
input.currentGameController = nil

input.currentPanGestureState = nil
input.currentPinchGestureState = nil
Expand Down Expand Up @@ -146,7 +146,7 @@ final class InputSystemTests: XCTestCase {
// MARK: - GamePadState (no hardware)

func test_manual_gamepad_state_flip() {
var gp = InputSystem.shared.gamePadState
var gp = InputSystem.shared.gameControllerState
XCTAssertFalse(gp.aPressed)
XCTAssertFalse(gp.bPressed)
XCTAssertFalse(gp.leftThumbStickActive)
Expand Down
Loading
Loading