// 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;
}
}
}