Documentation
¶
Overview ¶
Package syncsim provides a discrete-event simulator for testing the phcsync controller.
Architecture ¶
The simulator uses an event-driven architecture built on Go 1.23 iterators:
generatePulseEvents() ─┐ generateMessageEvents() ─┼─> mergeEvents() ──> for event := range events generateTickEvents() ─┘
Three event streams are generated independently and merged chronologically:
- Pulse events: GPS PPS edges (rising, and trailing for dual-edge mode)
- Message events: GPS time messages containing TAI time
- Tick events: System ticks every 250ms (like real daemon)
Key Design Principles ¶
1. Event generators are declarative - they describe WHAT events happen and WHEN, but NOT which events are delivered. The main loop decides delivery based on outages.
2. Pulse timestamps are ALWAYS read (even during outages) to track PHC drift during signal loss. The decision to deliver to the controller happens separately.
3. Ticks start immediately at t=0.25, modeling real system behavior. Early ticks are safe because the controller returns early when in ModeReset.
4. Mode tracking uses the observer pattern, which sees samples BEFORE mode transitions. This correctly attributes samples to the mode that processed them.
5. Single-edge and dual-edge modes are handled uniformly - the generator produces 1 or 2 pulse events per PPS, and the event loop processes them identically.
Index ¶
- Constants
- func Generate(cfg Config, w io.Writer) error
- func InOutage(outages []OutageConfig, t float64) bool
- func LoadConfig(path string, cfg *Config) error
- func WriteDefaultConfig(w io.Writer) error
- type AR1Config
- type AR1FMConfig
- type Config
- type DriftConfig
- type Event
- type EventType
- type ExcursionConfig
- type FaultConfig
- type FreqSinusoid
- type GPSConfig
- type MsgConfig
- type NavSolutionMsgEventData
- type OutageConfig
- type OutlierConfig
- type PHCConfig
- type PhaseSinusoid
- type PostPulseMsgEventData
- type PrePulseMsgEventData
- type PulseConfig
- type PulseEventData
- type RampConfig
- type ResonatorConfig
- type SawtoothConfig
- type SawtoothType
- type Seconds
- type SimConfig
- type Stats
- type TickEventData
Constants ¶
const (
NoPulse = gpsprot.NavSolution // use NavSolution value to indicate no sawtooth messages
)
Variables ¶
This section is empty.
Functions ¶
func Generate ¶
Generate outputs timestamps as JSON Lines to w. It generates PHC and/or GPS timestamps based on which config sections are non-zero.
func InOutage ¶
func InOutage(outages []OutageConfig, t float64) bool
InOutage returns true if time t falls within any outage period. outages must be pre-normalized (sorted, merged, no zero-duration).
func LoadConfig ¶
LoadConfig loads configuration from a TOML file into cfg. The caller initializes cfg (zero-valued or with defaults), and TOML values are merged on top.
func WriteDefaultConfig ¶
WriteDefaultConfig writes the default configuration as TOML to w. The output includes comments derived from struct field comment tags.
Types ¶
type AR1Config ¶
type AR1Config struct {
// Tau is the correlation time constant in seconds.
Tau Seconds `toml:"tau" check:">=0,<=1000000" comment:"Correlation time constant (s)"`
// Sigma is the steady-state RMS in nanoseconds.
Sigma clocksim.Nanoseconds `toml:"sigma" check:">=0,<=10000" comment:"Steady-state RMS (ns)"`
}
AR1Config configures an AR(1) phase noise process.
func (AR1Config) AlphaNoise ¶
func (c AR1Config) AlphaNoise() (alpha float64, noise clocksim.Nanoseconds)
AlphaNoise returns (alpha, noise_stddev) for use with AR1ColoredNoiseGPS. alpha is the autocorrelation coefficient, noise is the driving noise stddev in nanoseconds. Assumes 1-second sample interval.
type AR1FMConfig ¶
type AR1FMConfig struct {
// Tau is the correlation time constant in seconds.
Tau Seconds `toml:"tau" check:">=0,<=1000000" comment:"Correlation time constant (s)"`
// Sigma is the steady-state RMS of frequency bias in ppb.
Sigma clocksim.PPB `toml:"sigma" check:">=0,<=10000" comment:"Steady-state RMS of frequency bias (ppb)"`
}
AR1FMConfig configures an AR(1) frequency modulation process.
type Config ¶
type Config struct {
Sim SimConfig `toml:"sim" comment:"Simulation parameters"`
PHC PHCConfig `toml:"phc" comment:"PHC oscillator error model"`
GPS GPSConfig `toml:"gps" comment:"GPS time pulse error model"`
Sync phcsync.Config `toml:"sync" comment:"PHC sync controller parameters"`
Pulse PulseConfig `toml:"pulse" comment:"Pulse delivery timing"`
Msg MsgConfig `toml:"msg" comment:"GPS message delivery timing"`
Fault FaultConfig `toml:"fault" comment:"Fault injection parameters"`
}
Config holds simulation parameters
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with zero noise but sensible operational defaults. PHC and GPS have zero noise parameters (user specifies exactly what noise they want). Pulse and Msg have reasonable timing defaults. Fault has no faults configured.
type DriftConfig ¶
type DriftConfig struct {
// Tau is the characteristic timescale in seconds.
Tau Seconds `toml:"tau" check:">=0,<=1000000" comment:"Characteristic timescale (s)"`
// Sigma is the RMS phase deviation in nanoseconds.
Sigma clocksim.Nanoseconds `toml:"sigma" check:">=0,<=10000" comment:"RMS phase deviation (ns)"`
// Zeta is the damping ratio.
Zeta float64 `toml:"zeta" check:">=0,<=10" comment:"Damping ratio"`
}
DriftConfig configures bounded drift (Carpenter-Lee 2nd-order Gauss-Markov).
func DefaultDriftConfig ¶
func DefaultDriftConfig() DriftConfig
DefaultDriftConfig returns a DriftConfig with sensible zeta default.
func (DriftConfig) InternalParams ¶
func (c DriftConfig) InternalParams() (omegaN, zeta, sigmaDrift float64)
InternalParams converts user-facing (tau, sigma, zeta) to internal (omega_n, zeta, sigma_drift). Uses discrete Lyapunov calibration so that Sigma matches the actual RMS of the 1 Hz discrete drift simulator output.
type ExcursionConfig ¶
type ExcursionConfig struct {
// StartTime is when the excursion begins in seconds.
StartTime Seconds `toml:"startTime" check:">=0" comment:"When excursion begins (s)"`
// Duration is the total excursion duration including ramps in seconds.
Duration Seconds `toml:"duration" check:">=0" comment:"Total duration including ramps (s)"`
// Amplitude is the peak phase shift in nanoseconds.
Amplitude clocksim.Nanoseconds `toml:"amplitude" comment:"Peak phase shift (ns)"`
// Rise configures the rising edge ramp.
Rise RampConfig `toml:"rise" comment:"Rising edge ramp"`
// Fall configures the falling edge ramp.
Fall RampConfig `toml:"fall" comment:"Falling edge ramp"`
}
ExcursionConfig configures a temporary phase excursion with smooth ramps.
func DefaultExcursionConfig ¶
func DefaultExcursionConfig() ExcursionConfig
DefaultExcursionConfig returns an ExcursionConfig with sensible defaults.
func (ExcursionConfig) IsZero ¶
func (c ExcursionConfig) IsZero() bool
IsZero returns true if the excursion has no effect (zero amplitude).
type FaultConfig ¶
type FaultConfig struct {
Outage []OutageConfig `toml:"outage" comment:"Signal outage periods"`
Outlier []OutlierConfig `toml:"outlier" comment:"Discrete phase outlier injection"`
Excursion []ExcursionConfig `toml:"excursion" comment:"Temporary phase excursions"`
}
FaultConfig configures fault injection for testing controller resilience.
func DefaultFaultConfig ¶
func DefaultFaultConfig() FaultConfig
DefaultFaultConfig returns a FaultConfig with no faults configured. Includes zero entries so users can see the expected TOML structure.
func (*FaultConfig) NormalizeOutages ¶
func (c *FaultConfig) NormalizeOutages() []OutageConfig
NormalizeOutages returns a sorted, merged list of non-overlapping outages. Zero-duration outages are removed. The result can be used with InOutage().
type FreqSinusoid ¶
type FreqSinusoid struct {
// Period is the oscillation period in seconds.
Period Seconds `toml:"period" check:">0,<=1000000" comment:"Oscillation period (s)"`
// Amp is the frequency amplitude in parts-per-billion.
Amp clocksim.PPB `toml:"amp" check:">=0,<=10000" comment:"Frequency amplitude (ppb)"`
// PhaseInit is the initial phase as a fraction of cycle [0,1).
PhaseInit float64 `toml:"phaseInit" check:">=0,<1" comment:"Initial phase [0,1)"`
}
FreqSinusoid configures a sinusoidal frequency modulation component. Contribution to frequency: Amp * sin(2π * (t/Period + PhaseInit))
func (FreqSinusoid) IsZero ¶
func (s FreqSinusoid) IsZero() bool
type GPSConfig ¶
type GPSConfig struct {
// Jitter is white phase noise stddev in nanoseconds.
// Typical: 0.1-5 ns survey-grade, 5-50 ns consumer-grade.
Jitter clocksim.Nanoseconds `toml:"jitter" check:">=0,<=1000" comment:"White phase noise stddev (ns)"`
// Sawtooth configures GPS receiver quantization sawtooth error.
Sawtooth SawtoothConfig `toml:"sawtooth" comment:"GPS receiver quantization sawtooth error"`
// AR1 is a list of AR(1) colored phase noise processes.
AR1 []AR1Config `toml:"ar1" comment:"AR(1) colored phase noise processes"`
// AR1FM is an AR(1) frequency modulation process (sigma in ppb).
AR1FM AR1FMConfig `toml:"ar1FM" comment:"AR(1) frequency modulation process"`
// RandomWalk is the random walk FM coefficient in ppb/√s.
RandomWalk float64 `toml:"randomWalk" check:">=0,<=1000" comment:"Random walk FM coefficient (ppb/√s)"`
// Drift configures bounded drift (2nd-order Gauss-Markov).
Drift DriftConfig `toml:"drift" comment:"Bounded drift (2nd-order Gauss-Markov)"`
// Resonator configures a damped oscillator on phase.
Resonator ResonatorConfig `toml:"resonator" comment:"Damped oscillator on phase"`
// Sinusoid is a list of sinusoidal phase modulation components.
Sinusoid []PhaseSinusoid `toml:"sinusoid" comment:"Sinusoidal phase modulation components"`
}
GPSConfig configures the GPS PPS timing error model. All phase parameters are in nanoseconds.
func DefaultGPSConfig ¶
func DefaultGPSConfig() GPSConfig
DefaultGPSConfig returns a GPSConfig with zero noise but sensible defaults. Users specifying sawtooth.amp will get working InternalClock values automatically. Drift and Resonator have sensible zeta defaults for when user specifies tau/sigma. Includes zero AR1 and Sinusoid entries so users can see the expected structure.
func (GPSConfig) CreateSimulator ¶
func (c GPSConfig) CreateSimulator(startTime toml.LocalTime) clocksim.GPSSimulator
CreateSimulator returns a GPSSimulator combining all GPS error sources. Applies components in order: jitter, AR(1), AR(1) FM, random walk, drift, sinusoids. Does NOT include Shift, Outlier, or Sawtooth - those are added separately in Simulate(). Sawtooth is created separately with oscillator coupling. startTime is the wall-clock time for t=0, used with PeakAt to calculate sinusoid phase.
type MsgConfig ¶
type MsgConfig struct {
// Delay is the mean delay from the PPS pulse to when the GPS time message
// is received. Real GPS receivers take 50-250ms to transmit after the pulse.
Delay Seconds `toml:"delay" check:">=0,<1" comment:"Mean message delay after pulse (s)"`
// Jitter is the standard deviation of the message arrival time.
Jitter Seconds `toml:"jitter" check:">=0,<1" comment:"Message delay stddev (s)"`
// SawtoothType specifies how sawtooth correction messages are delivered:
// - SawtoothPrePulse ("prepulse"): Correction arrives before the pulse (UBX-TIM-TP)
// - SawtoothPostPulse ("postpulse"): Correction arrives after the pulse (UBX-TIM-TOS)
// - SawtoothNone ("none"): No correction messages
SawtoothType SawtoothType `toml:"sawtoothType" comment:"How sawtooth correction is delivered (prepulse/postpulse/none)"`
// PrePulseTime is the time before the PPS edge when a PrePulse correction
// message is delivered. Only used when SawtoothType is SawtoothPrePulse.
PrePulseTime Seconds `toml:"prePulseTime" check:">=0,<1" comment:"Time before pulse for prepulse message (s)"`
// PostPulseDelay is the delay after the PPS edge when a PostPulse correction
// message is delivered. Only used when SawtoothType is SawtoothPostPulse.
PostPulseDelay Seconds `toml:"postPulseDelay" check:">=0,<1" comment:"Delay after pulse for postpulse message (s)"`
}
MsgConfig configures GPS message delivery timing. GPS receivers send time messages (e.g., UBX-NAV-PVT) after each PPS pulse.
func DefaultMsgConfig ¶
func DefaultMsgConfig() MsgConfig
DefaultMsgConfig returns default message timing parameters.
type NavSolutionMsgEventData ¶
type NavSolutionMsgEventData struct {
}
type OutageConfig ¶
type OutageConfig struct {
// StartTime is when the outage begins in seconds.
StartTime Seconds `toml:"startTime" check:">=0" comment:"When outage begins (s)"`
// Duration is the outage duration in seconds.
Duration Seconds `toml:"duration" check:">=0" comment:"Outage duration (s)"`
}
OutageConfig configures a signal outage period.
type OutlierConfig ¶
type OutlierConfig struct {
// Time is the simulation time at which to inject an outlier.
Time Seconds `toml:"time" check:">=0" comment:"When to inject outlier (s)"`
// Offset is the magnitude of the phase offset to inject in nanoseconds.
// Typical multipath outliers are 1000-10000 ns (1-10 µs).
Offset clocksim.Nanoseconds `toml:"offset" comment:"Outlier offset magnitude (ns)"`
}
OutlierConfig configures a discrete phase outlier injection.
func (OutlierConfig) IsZero ¶
func (c OutlierConfig) IsZero() bool
type PHCConfig ¶
type PHCConfig struct {
// FreqOffset is the constant frequency offset in ppb.
// Typical values: ±1000-10000 ppb for factory-trimmed oscillators.
FreqOffset clocksim.PPB `toml:"freqOffset" check:">=-1_000_000,<=1_000_000" comment:"Constant frequency offset (ppb)"`
// Drift is the linear frequency drift rate in ppb per day.
Drift float64 `toml:"drift" check:">=-1_000_000,<=1_000_000" comment:"Linear frequency drift (ppb/day)"`
// WhiteNoise is the standard deviation of white frequency noise in ppb.
// Typical values are 1-20 ppb for good crystals.
WhiteNoise clocksim.PPB `toml:"whiteNoise" check:">=0,<=10000" comment:"White frequency noise stddev (ppb)"`
// FlickerNoise is the standard deviation of flicker (1/f) frequency noise in ppb.
// Typical values are 0.1-5 ppb for crystals.
FlickerNoise clocksim.PPB `toml:"flickerNoise" check:">=0,<=10000" comment:"Flicker (1/f) frequency noise stddev (ppb)"`
// RandomWalk is the random walk FM coefficient in ppb/√s.
// Typical values are 0.01-1 ppb/√s.
RandomWalk clocksim.PPB `toml:"randomWalk" check:">=0,<=10000" comment:"Random walk FM coefficient (ppb/√s)"`
// Sinusoid is a list of sinusoidal frequency modulation components.
Sinusoid []FreqSinusoid `toml:"sinusoid" comment:"Sinusoidal frequency modulation components"`
}
PHCConfig configures the PTP Hardware Clock oscillator error model. The PHC's instantaneous frequency is modeled as:
nominal_freq * (1 + freq_offset + drift*t + white + flicker + random_walk + sinusoids)
All frequency parameters are in parts-per-billion (ppb) relative to nominal.
func DefaultPHCConfig ¶
func DefaultPHCConfig() PHCConfig
DefaultPHCConfig returns a PHCConfig with zero noise. Includes a zero sinusoid entry so users can see the expected structure.
func (PHCConfig) CreateSimulator ¶
func (c PHCConfig) CreateSimulator() clocksim.OscSimulator
CreateSimulator returns an OscSimulator combining all PHC error sources. Applies components in order: offset, white noise, flicker noise, random walk, drift, sinusoids.
type PhaseSinusoid ¶
type PhaseSinusoid struct {
// Period is the oscillation period in seconds.
Period Seconds `toml:"period" check:">0,<=1000000" comment:"Oscillation period (s)"`
// Amp is the phase amplitude in nanoseconds.
Amp clocksim.Nanoseconds `toml:"amp" check:">=0,<=10000" comment:"Phase amplitude (ns)"`
// PeakAt is the time of day when the sinusoid peaks.
// Used with SimConfig.StartTime to calculate initial phase.
// Defaults to 06:00:00, which gives phase 0 when StartTime is 00:00:00.
PeakAt toml.LocalTime `toml:"peakAt" comment:"Time of day when sinusoid peaks (HH:MM:SS)"`
}
PhaseSinusoid configures a sinusoidal phase modulation component. Contribution to phase: Amp * sin(2π * (t/Period + PhaseInit()))
func (PhaseSinusoid) IsZero ¶
func (s PhaseSinusoid) IsZero() bool
func (PhaseSinusoid) PhaseInit ¶
func (s PhaseSinusoid) PhaseInit(startTimeSec float64) float64
PhaseInit returns the initial phase [0,1) for this sinusoid. Calculates phase so sinusoid peaks at PeakAt given startTimeSec.
type PostPulseMsgEventData ¶
type PostPulseMsgEventData struct {
PPS float64
}
type PrePulseMsgEventData ¶
type PulseConfig ¶
type PulseConfig struct {
// MinDelay is the minimum delay from the true GPS second to when the
// PPS pulse edge is delivered to the PHC timestamper. Real GPS receivers
// have internal processing delays that prevent the pulse from arriving
// exactly at the second boundary. The actual delay for each pulse is
// uniformly distributed between MinDelay and MaxDelay.
// Typical values are 5-50 microseconds (0.000005 to 0.00005).
MinDelay Seconds `toml:"minDelay" check:">=0,<1" comment:"Min pulse delay from GPS second (s)"`
// MaxDelay is the maximum delay from the true GPS second to when the
// PPS pulse edge is delivered to the PHC timestamper. Must be >= MinDelay.
MaxDelay Seconds `toml:"maxDelay" check:">=0,<1" comment:"Max pulse delay from GPS second (s)"`
// Width is the pulse width for dual-edge timestamping mode.
// When Width > 0, the simulator generates both rising and falling edges.
// When Width = 0, only rising edges are generated (single-edge mode).
// Typical GPS receivers use 100ms (0.1) pulse width.
Width Seconds `toml:"width" check:">=0,<1" comment:"Pulse width (0=single-edge mode)"`
}
PulseConfig configures PPS pulse timing characteristics.
func DefaultPulseConfig ¶
func DefaultPulseConfig() PulseConfig
DefaultPulseConfig returns default pulse timing parameters.
type PulseEventData ¶
type RampConfig ¶
type RampConfig struct {
// Duration is the ramp duration in seconds. 0 means instant transition.
Duration Seconds `toml:"duration" check:">=0" comment:"Ramp duration (s)"`
// Power controls the ramp shape. nil defaults to 2.0 (smooth S-curve).
// power > 1 gives smooth acceleration/deceleration at endpoints.
// power = 1 is linear.
Power *float64 `toml:"power" check:">0,<=10" comment:"Shape power (>1 for smooth S-curve)"`
}
RampConfig configures a smooth ramp transition.
func (RampConfig) EffectivePower ¶
func (c RampConfig) EffectivePower() float64
EffectivePower returns the power value, defaulting to 2.0 if nil.
type ResonatorConfig ¶
type ResonatorConfig struct {
// Period is the natural oscillation period in seconds.
Period Seconds `toml:"period" check:">=0,<=1000000" comment:"Natural oscillation period (s)"`
// Sigma is the RMS phase deviation in nanoseconds.
Sigma clocksim.Nanoseconds `toml:"sigma" check:">=0,<=10000" comment:"RMS phase deviation (ns)"`
// Zeta is the damping ratio.
Zeta float64 `toml:"zeta" check:">=0,<=10" comment:"Damping ratio"`
}
ResonatorConfig configures a damped harmonic oscillator on phase. Implements a bounded phase process that oscillates around zero.
func DefaultResonatorConfig ¶
func DefaultResonatorConfig() ResonatorConfig
DefaultResonatorConfig returns a ResonatorConfig with sensible zeta default.
func (ResonatorConfig) InternalParams ¶
func (c ResonatorConfig) InternalParams() (omegaN, zeta, sigmaNoise float64)
InternalParams converts user-facing (period, sigma, zeta) to internal (omegaN, zeta, sigmaNoise).
type SawtoothConfig ¶
type SawtoothConfig struct {
// Amp is the sawtooth amplitude in nanoseconds (≈ 0.5e9/f_osc).
Amp float64 `toml:"amp" check:">=0,<=1000" comment:"Sawtooth amplitude (ns, ≈ 0.5e9/f_osc)"`
// PhaseInit is the initial phase [0,1), default 0.5.
PhaseInit float64 `toml:"phaseInit" check:">=0,<1" comment:"Initial phase [0,1)"`
// InternalClock models the GPS receiver's internal oscillator error.
InternalClock FreqSinusoid `toml:"internalClock" comment:"GPS internal oscillator error model"`
}
SawtoothConfig configures GPS receiver quantization sawtooth error.
func DefaultSawtoothConfig ¶
func DefaultSawtoothConfig() SawtoothConfig
DefaultSawtoothConfig returns a SawtoothConfig with zero amplitude but sensible defaults for PhaseInit and InternalClock. Users specifying sawtooth.amp will get working values.
type SawtoothType ¶
type SawtoothType int
SawtoothType specifies how sawtooth correction messages are delivered.
const ( SawtoothPrePulse SawtoothType = iota // correction before pulse (UBX-TIM-TP) SawtoothPostPulse // correction after pulse (UBX-TIM-TOS) SawtoothNone // no correction messages )
func (SawtoothType) MarshalText ¶
func (s SawtoothType) MarshalText() ([]byte, error)
MarshalText implements encoding.TextMarshaler for TOML serialization.
func (SawtoothType) String ¶
func (s SawtoothType) String() string
func (SawtoothType) TimeRef ¶
func (s SawtoothType) TimeRef() gpsprot.TimeRef
TimeRef converts SawtoothType to gpsprot.TimeRef for use with timemsg.Buffer.
func (*SawtoothType) UnmarshalText ¶
func (s *SawtoothType) UnmarshalText(text []byte) error
UnmarshalText implements encoding.TextUnmarshaler for TOML deserialization.
type Seconds ¶
type Seconds = float64
Seconds is a true alias for float64, used for documentation in config structs. No conversion needed - clocksim uses float64 for seconds internally.
type SimConfig ¶
type SimConfig struct {
// Duration is the simulation duration in seconds.
Duration Seconds `toml:"duration" check:">0" comment:"Simulation duration (s)"`
// StartTime is the wall-clock time of day for simulation t=0.
// Used with PhaseSinusoid.PeakAt to calculate initial phase.
// Defaults to 00:00:00 (midnight).
StartTime toml.LocalTime `toml:"startTime" comment:"Wall-clock time for t=0 (HH:MM:SS)"`
}
SimConfig configures simulation parameters.
func DefaultSimConfig ¶
func DefaultSimConfig() SimConfig
DefaultSimConfig returns a SimConfig with a reasonable default duration.
type Stats ¶
type Stats struct {
statsobs.Stats // embedded - detailed tracking statistics from observer
SampleCount int // total samples fed to controller
TrackingStdDev float64 // stddev from true time in nanoseconds (simulation-only)
TrackingMean float64 // mean offset from true time in nanoseconds (simulation-only)
TrackingAbsMax time.Duration // max absolute offset from true time (simulation-only)
TrackingADev float64 // Allan deviation of tracking offsets (simulation-only)
ModeSamples map[phcsync.Mode]int // samples per mode (non-zero only)
ModeTransitions map[phcsync.Mode]int // transitions into each mode (non-zero only)
}
Stats holds simulation results
func Simulate ¶
func Simulate(observers []obs.Observer, cfg Config, tsLog io.Writer, curTime *time.Time, lg *slog.Logger) (Stats, error)
Simulate runs a phcsync simulation with the given configuration. tsLog is an optional writer for PHC timestamp log (JSON Lines format). curTime is updated as the simulation progresses, allowing callers to use it for logging. It returns statistics about the simulation run.
type TickEventData ¶
type TickEventData struct {
}