Files
luma/Sources/LumaCore/EventLog.swift
Ole André Vadla Ravnås f3064ef002 Persist projects via working-copy snapshots
GTK now opens each document into a fresh working copy and exports
a VACUUM INTO snapshot to the user-facing path on save and on
close. Hooks stay attached across Save As since the engine never
moves. The window close handler holds the GApplication while the
snapshot and shutdown drain.

Engine.shutdown flushes EventLog and awaits the chained
event-store writes so events.log is durable.

LumaProject and ProjectAutosaver share the snapshot helper.
2026-05-21 10:15:20 +02:00

91 lines
2.7 KiB
Swift

import Observation
@Observable
@MainActor
public final class EventLog {
public private(set) var events: [RuntimeEvent] = []
public private(set) var totalReceived: Int = 0
public private(set) var flushVersion: Int = 0
public let maxVisible: Int
public let maxInMemory: Int
@ObservationIgnored public var onEventsAppended: ((@MainActor (ArraySlice<RuntimeEvent>) -> Void))?
@ObservationIgnored public var onEventsCleared: ((@MainActor () -> Void))?
@ObservationIgnored public var onEventsToPersist: ((@MainActor ([RuntimeEvent]) -> Void))?
@ObservationIgnored private var allEvents: [RuntimeEvent] = []
@ObservationIgnored private var pendingPersist: [RuntimeEvent] = []
@ObservationIgnored private var isFlushScheduled = false
@ObservationIgnored private var lastFlushedCount = 0
public init(maxVisible: Int = 1_000, maxInMemory: Int = 10_000) {
self.maxVisible = maxVisible
self.maxInMemory = maxInMemory
}
public func append(_ event: RuntimeEvent) {
totalReceived += 1
allEvents.append(event)
pendingPersist.append(event)
if allEvents.count > maxInMemory {
allEvents.removeFirst(allEvents.count - maxInMemory)
}
scheduleFlush()
}
public func restore(_ events: [RuntimeEvent]) {
allEvents = Array(events.suffix(maxInMemory))
totalReceived = events.count
let visible = Array(allEvents.suffix(maxVisible))
self.events = visible
lastFlushedCount = visible.count
flushVersion &+= 1
}
public func clear() {
allEvents.removeAll()
events.removeAll()
pendingPersist.removeAll()
totalReceived = 0
flushVersion = 0
lastFlushedCount = 0
isFlushScheduled = false
onEventsCleared?()
}
public func flushNow() {
isFlushScheduled = false
flush()
}
private func scheduleFlush() {
guard !isFlushScheduled else { return }
isFlushScheduled = true
Task { [weak self] in
try? await Task.sleep(nanoseconds: 16_000_000)
self?.flush()
}
}
private func flush() {
isFlushScheduled = false
let visible = Array(allEvents.suffix(maxVisible))
let newCount = visible.count
let prevCount = lastFlushedCount
events = visible
lastFlushedCount = newCount
flushVersion &+= 1
if newCount > prevCount {
onEventsAppended?(visible[prevCount...])
}
if !pendingPersist.isEmpty, let onEventsToPersist {
let batch = pendingPersist
pendingPersist.removeAll(keepingCapacity: true)
onEventsToPersist(batch)
}
}
}