rrbox / ecs-swift

Entity Component System for swift
MIT License
3 stars 0 forks source link

EventWriter と EventReader #2

Closed rrbox closed 4 months ago

rrbox commented 11 months ago
rrbox commented 11 months ago

EventWriter

class EventWriter<T> {

}
rrbox commented 11 months ago

EventReader

class EventReader<T> {

}
rrbox commented 11 months ago

💡Combine の pass through subject を使うことができるかもしれないです

rrbox commented 11 months ago
class EventCommand {
    func runEventResponder(in world: World) {

    }
}

class EventSystemExecute<T>: SystemExecute {
    func receive(event: EventReader<T>, worldBuffer: WorldBuffer) {

    }
}

class EventSystem<T, Parameter: SystemParameter>: EventSystemExecute<T> {

}

extension World {
    @discardableResult func addEventSystem<T, Parameter: SystemParameter>(_ system: EventSystem<T, Parameter>) -> World {
        self.worldBuffer.systemBuffer.addSystem(system, as: EventSystemExecute<T>.self)
        return self
    }

    func receive<T>(event: EventReader<T>) {
        for system in self.worldBuffer.systemBuffer.systems(ofType: EventSystemExecute<T>.self) {
            system.receive(event: event, worldBuffer: self.worldBuffer)
        }
    }
}

public struct EventReader<T> {
    public let value: T
}

class Event<T>: EventCommand {
    let value: T
    init(value: T) {
        self.value = value
    }

    override func runEventResponder(in world: World) {
        world.receive(event: EventReader(value: self.value))
    }
}

// world buffer に追加しておく (world + Init)
class EventQueue: BufferElement {
    var eventCommandQueue = [EventCommand]()
}

// world buffer にプロパティをつけておく
class EventQueueBuffer {
    let buffer: Buffer
    init(buffer: Buffer) {
        self.buffer = buffer
    }

    func eventQueue() -> EventQueue {
        self.buffer.component(ofType: EventQueue.self)!
    }
}

// Commands と基本的な仕組みは同じ.
final public class EventWriter<T>: SystemParameter {
    let eventQueue: EventQueue
    init(eventQueue: EventQueue) {
        self.eventQueue = eventQueue
    }

    public func push(_ event: T) {
        self.eventQueue.eventCommandQueue.append(Event<T>(value: event))
    }

    public static func register(to worldBuffer: WorldBuffer) {

    }

    public static func getParameter(from worldBuffer: WorldBuffer) -> EventWriter<T>? {
        EventWriter<T>(eventQueue: EventQueueBuffer(buffer: worldBuffer).eventQueue())
    }
}

ここまで作ってみましたが、World に buffer 内に System の保管領域を確保する際につまづいてしまいました。他の方法を検討してみたいです。

rrbox commented 11 months ago

Combine を使用した方法も試してみました。

import Combine
import Foundation

public class EventExecute<T>: SystemExecute {
    var cancellable: AnyCancellable?

    func receiveEvent(_ event: T, worldBuffer: WorldBuffer) {

    }

    func setSubject(from buffer: WorldBuffer) {
        let subject = buffer.eventSubjectBuffer.getSubject(eventValueOfType: T.self)
        self.cancellable = subject!.sink { [weak self, weak buffer] event in
            self!.receiveEvent(event, worldBuffer: buffer!)
        }
    }
}

public struct EventReader<T> {
    public let value: T
}

final public class EventSystem<T, Parameter: SystemParameter>: EventExecute<T> {
    let execute: (EventReader<T>, Parameter) -> ()

    public init(execute: @escaping (EventReader<T>, Parameter) -> ()) {
        self.execute = execute
    }

    override func receiveEvent(_ event: T, worldBuffer: WorldBuffer) {
        self.execute(EventReader(value: event), Parameter.getParameter(from: worldBuffer)!)
    }

}

class EventSubjectBuffer {
    class EventSubjectRegistry<T>: BufferElement {
        let subject: PassthroughSubject<T, Never>
        init(subject: PassthroughSubject<T, Never>) {
            self.subject = subject
            super.init()
        }

        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }

    let buffer: Buffer
    init(buffer: Buffer) {
        self.buffer = buffer
    }

    func registerSubject<T>(eventValueOfType type: T.Type) {
        self.buffer.addComponent(EventSubjectRegistry<T>(subject: PassthroughSubject<T, Never>()))
    }

    func getSubject<T>(eventValueOfType type: T.Type) -> PassthroughSubject<T, Never>? {
        self.buffer.component(ofType: EventSubjectRegistry<T>.self)?.subject
    }
}

extension WorldBuffer {
    var eventSubjectBuffer: EventSubjectBuffer {
        EventSubjectBuffer(buffer: self)
    }
}

final public class EventWriter<T>: SystemParameter {
    let subject: PassthroughSubject<T, Never>

    init(subject: PassthroughSubject<T, Never>) {
        self.subject = subject
    }

    public func send(value: T) {
        self.subject.send(value)
    }

    public static func register(to worldBuffer: WorldBuffer) {

    }

    public static func getParameter(from worldBuffer: WorldBuffer) -> EventWriter<T>? {
        EventWriter<T>(subject: worldBuffer.eventSubjectBuffer.getSubject(eventValueOfType: T.self)!)
    }
}

public extension World {
    @discardableResult func addEventStreamer<T>(eventType: T.Type) -> World {
        self.worldBuffer.systemBuffer.registerSystemRegistry(ofType: EventExecute<T>.self)
        self.worldBuffer.eventSubjectBuffer.registerSubject(eventValueOfType: T.self)
        return self
    }
}

public extension World {
    @discardableResult func addEventSystem<T, Parameter: SystemParameter>(_ system: EventSystem<T, Parameter>) -> World {
        system.setSubject(from: self.worldBuffer)
        self.worldBuffer.systemBuffer.addSystem(system, as: EventExecute<T>.self)
        Parameter.register(to: self.worldBuffer)
        return self
    }

    @discardableResult func addEventSystem<T, Parameter: SystemParameter>(_ execute: @escaping (EventReader<T>, Parameter) -> ()) -> World {
        self.addEventSystem(EventSystem(execute: execute))
    }
}

この実装は比較的うまく機能しますが、問題点もあります。それは、Combine が非同期的に実行されることによる、データ競合です。主に Commands の commandQueue で競合が起こるようです。Commands の仕様を非同期処理に強い設計にすればなんとかなりそうな気がします。

また、 API をある程度妥協して設計しました。同じようにすれば、一つ上のコメントの実装でもできるかもしれません。

rrbox commented 11 months ago

Event system 内で event writer を操作できない不具合があるようです。 理由は、そもそも Event system 実行中に Event を追加しても、Event system 実行終了後に全て削除されてしまうためです。

解決策は以下の通りです。

rrbox commented 10 months ago

処理中 event buffer を作成する方法を採用してみます。

rrbox commented 4 months ago

0.1 で完全に実装しました。

event 発生後は world に一旦保管し、event builder でセットされたシステムが実行される際に消費されるようにしました。