rrbox / ecs-swift

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

Query を Swift Macros で実装する #89

Closed rrbox closed 2 months ago

rrbox commented 3 months ago

Macro 宣言・隠蔽側

@freestanding(declaration, names: arbitrary)
macro Query(_ c: Int) = #externalMacro(module: "MacroModule", type: "QueryMacro")

public enum Queies {
    #Query(2)
    #Query(3)
    #Query(4)
    #Query(5)
    #Query(6)
    #Query(7)
    #Query(8)
    #Query(9)
    #Query(10)
}

public typealias Query2 = Queies.Query2
public typealias Query3 = Queies.Query3
public typealias Query4 = Queies.Query4
public typealias Query5 = Queies.Query5
public typealias Query6 = Queies.Query6
public typealias Query7 = Queies.Query7
public typealias Query8 = Queies.Query8
public typealias Query9 = Queies.Query9
public typealias Query10 = Queies.Query10
rrbox commented 3 months ago

Macro 実装

struct QueryMacro: DeclarationMacro {
    static func expansion(
        of node: some FreestandingMacroExpansionSyntax,
        in context: some MacroExpansionContext
    ) throws -> [DeclSyntax] {
        guard let argument = node.argumentList.first?.expression else {
            fatalError("compiler bug: the macro does not have any arguments")
        }
        guard let intArg = argument.as(IntegerLiteralExprSyntax.self)?.literal else {
            fatalError("compiler bug: argument is not integer literal")
        }
        let n = Int(intArg.text)!

        let genericArguments = (0..<n).reduce(into: "") { partialResult, i in
            partialResult.append("C\(i): Component, ")
        }.dropLast(2)
        let valueRefs = (0..<n).reduce(into: "") { partialResult, i in
            partialResult.append("Ref<C\(i)>, ")
        }.dropLast(2)
        let parameterTypes = (0..<n).enumerated().reduce(into: "") { partialResult, i in
            partialResult.append("_ p\(i.offset): inout C\(i.element), ")
        }.dropLast(2)
        let componentRefs = (0..<n).indices.reduce(into: "") { partialResult, i in
            partialResult.append("&components.\(i).value, ")
        }.dropLast(2)

        return [
            """
            public struct Query\(raw: n)<\(raw: genericArguments)> {
                public var components: SparseSet<\(raw: valueRefs)>(sparse: [], dense: [], data: [])

                public func allocate() {
                    self.components.allocate()
                }

                public func update(_ f: (\(raw: parameterTypes)) -> ()) {
                   self.values.forEach { components in
                       f(\(raw: componentRefs))
                   }
                }

                public init(values: [(\(raw: valueRefs))]) {
                    self.values = values
                }
            }
            """
        ]
    }
}
rrbox commented 3 months ago
final public class Query2<C0: QueryTarget, C1: QueryTarget>: Chunk, SystemParameter {
    var components = SparseSet<(Ref<C0>, Ref<C1>)>(sparse: [], dense: [], data: [])

    public override init() {}

    public func allocate() {
        self.components.allocate()
    }

    public func insert(entity: Entity, entityRecord: EntityRecordRef) {
        guard let c0 = entityRecord.ref(C0.self),
              let c1 = entityRecord.ref(C1.self) else { return }
        self.components.insert((c0, c1), withEntity: entity)
    }

    public override func spawn(entity: Entity, entityRecord: EntityRecordRef) {
        if entity.generation == 0 {
            self.components.allocate()
        }
        self.insert(entity: entity, entityRecord: entityRecord)
    }

    public override func despawn(entity: Entity) {
        guard self.components.contains(entity) else { return }
        self.components.pop(entity: entity)
    }

    override func applyCurrentState(_ entityRecord: EntityRecordRef, forEntity entity: Entity) {
        guard let c0 = entityRecord.ref(C0.self),
              let c1 = entityRecord.ref(C1.self) else {
            self.despawn(entity: entity)
            return
        }
        guard !components.contains(entity) else { return }
        self.components.insert((c0, c1), withEntity: entity)

    }

    /// Query で指定した Component を持つ entity を world から取得し, イテレーションします.
    public func update(_ f: (inout C0, inout C1) -> ()) {
        for d in self.components.data {
            f(&d.0.value, &d.1.value)
        }
    }

    public func update(_ entity: Entity, _ f: (inout C0, inout C1) -> ()) {
        guard let ref = self.components.value(forEntity: entity) else { return }
        f(&ref.0.value, &ref.1.value)
    }

    public func components(forEntity entity: Entity) -> (C0, C1)? {
        guard let references = components.value(forEntity: entity) else { return nil }
        return (references.0.value, references.1.value)
    }

    public static func register(to worldStorage: WorldStorageRef) {
        guard worldStorage.chunkStorage.chunk(ofType: Self.self) == nil else {
            return
        }

        let queryRegistory = Self()

        worldStorage.chunkStorage.addChunk(queryRegistory)

    }

    public static func getParameter(from worldStorage: WorldStorageRef) -> Self? {
        worldStorage.chunkStorage.chunk(ofType: Self.self)
    }

}
rrbox commented 2 months ago