// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later namespace SharpEmu.Debugger.Breakpoints; /// /// A thread-safe registry of breakpoints. The debug server mutates it from /// client-servicing threads while the emulation thread queries it at frame /// boundaries, so every operation takes the same lock. /// public sealed class BreakpointStore { private readonly object _sync = new(); private readonly Dictionary _breakpoints = new(); private int _nextId = 1; /// Adds a breakpoint and returns the created entry with its id. public Breakpoint Add(BreakpointKind kind, ulong address, ulong length = 1) { lock (_sync) { var effectiveLength = kind == BreakpointKind.Execute ? 1UL : Math.Max(1UL, length); var breakpoint = new Breakpoint(_nextId++, kind, address, effectiveLength); _breakpoints[breakpoint.Id] = breakpoint; return breakpoint; } } /// Removes a breakpoint by id. Returns false when it did not exist. public bool Remove(int id) { lock (_sync) { return _breakpoints.Remove(id); } } /// Enables or disables a breakpoint by id. public bool SetEnabled(int id, bool enabled) { lock (_sync) { if (!_breakpoints.TryGetValue(id, out var breakpoint)) { return false; } _breakpoints[id] = breakpoint.WithEnabled(enabled); return true; } } /// Removes every breakpoint. public void Clear() { lock (_sync) { _breakpoints.Clear(); } } /// Returns a point-in-time copy of all breakpoints. public IReadOnlyList Snapshot() { lock (_sync) { return _breakpoints.Values.ToArray(); } } /// /// Finds the first enabled execution breakpoint covering , /// or null when none applies. /// public Breakpoint? FindExecuteHit(ulong address) { lock (_sync) { foreach (var breakpoint in _breakpoints.Values) { if (breakpoint.Enabled && breakpoint.Kind == BreakpointKind.Execute && breakpoint.Covers(address)) { return breakpoint; } } return null; } } }