Packages
Terminal emulation and driver infrastructure for Raxol. ANSI parsing, screen buffers, command processing, cursor management, input handling, session management, and termbox2 NIF integration.
Current section
Files
Jump to
Current section
Files
lib/raxol/terminal/sync/system.ex
defmodule Raxol.Terminal.Sync.System do
@moduledoc """
Unified synchronization system for the terminal emulator.
Handles synchronization between splits, windows, and tabs with different consistency levels.
"""
use Raxol.Core.Behaviours.BaseManager
alias Raxol.Core.Runtime.Log
# Types
@type sync_id :: String.t()
@type sync_key :: String.t()
@type sync_value :: term()
@type sync_metadata :: %{
version: non_neg_integer(),
timestamp: non_neg_integer(),
source: String.t(),
consistency: :strong | :eventual | :causal
}
@type sync_entry :: %{
key: sync_key(),
value: sync_value(),
metadata: sync_metadata()
}
@type sync_stats :: %{
sync_count: non_neg_integer(),
conflict_count: non_neg_integer(),
last_sync: non_neg_integer(),
consistency_levels: %{atom() => non_neg_integer()}
}
# Client API
def sync(sync_id, key, value, opts \\ []) do
Log.debug(
"[System] sync called: sync_id=#{sync_id}, key=#{key}, value=#{inspect(value)}, opts=#{inspect(opts)}"
)
GenServer.call(__MODULE__, {:sync, sync_id, key, value, opts})
end
def get(sync_id, key) do
GenServer.call(__MODULE__, {:get, sync_id, key})
end
def get_all(sync_id) do
GenServer.call(__MODULE__, {:get_all, sync_id})
end
def delete(sync_id, key) do
GenServer.call(__MODULE__, {:delete, sync_id, key})
end
def clear(sync_id) do
GenServer.call(__MODULE__, {:clear, sync_id})
end
def stats(sync_id) do
GenServer.call(__MODULE__, {:stats, sync_id})
end
# Server Callbacks
@impl true
def init_manager(opts) do
# Convert keyword list to map if needed
opts_map = if Keyword.keyword?(opts), do: Map.new(opts), else: opts
state = %{
# sync_id => %{key => sync_entry}
syncs: %{},
# sync_id => sync_stats
stats: %{},
consistency_levels:
Map.get(opts_map, :consistency_levels, %{
split: :strong,
window: :strong,
tab: :eventual
})
}
{:ok, state}
end
@impl true
def handle_manager_call({:sync, sync_id, key, value, opts}, _from, state) do
# Convert keyword list to map if needed
opts_map = if Keyword.keyword?(opts), do: Map.new(opts), else: opts
consistency =
Map.get(
opts_map,
:consistency,
Map.get(state.consistency_levels, sync_id, :eventual)
)
metadata = %{
version: Map.get(opts_map, :version, System.monotonic_time()),
timestamp: System.system_time(),
source: Map.get(opts_map, :source, "unknown"),
consistency: consistency
}
case do_sync(state, sync_id, key, value, metadata) do
{:ok, new_state} ->
{:reply, :ok, new_state}
{:error, :conflict, new_state} ->
{:reply, {:error, :conflict}, new_state}
end
end
@impl true
def handle_manager_call({:get, sync_id, key}, _from, state) do
case get_sync_entry(state, sync_id, key) do
{:ok, entry} -> {:reply, {:ok, entry.value}, state}
{:error, reason} -> {:reply, {:error, reason}, state}
end
end
@impl true
def handle_manager_call({:get_all, sync_id}, _from, state) do
case Map.get(state.syncs, sync_id) do
nil -> {:reply, {:error, :not_found}, state}
sync_data -> {:reply, {:ok, sync_data}, state}
end
end
@impl true
def handle_manager_call({:delete, sync_id, key}, _from, state) do
new_state = delete_sync_entry(state, sync_id, key)
{:reply, :ok, new_state}
end
@impl true
def handle_manager_call({:clear, sync_id}, _from, state) do
new_state = clear_sync_entries(state, sync_id)
{:reply, :ok, new_state}
end
@impl true
def handle_manager_call({:stats, sync_id}, _from, state) do
case Map.get(state.stats, sync_id) do
nil -> {:reply, {:error, :not_found}, state}
stats -> {:reply, {:ok, stats}, state}
end
end
# Private Functions
defp do_sync(state, sync_id, key, value, metadata) do
case get_sync_entry(state, sync_id, key) do
{:ok, existing_entry} ->
handle_existing_sync(
state,
sync_id,
key,
value,
metadata,
existing_entry
)
{:error, :not_found} ->
handle_new_sync(state, sync_id, key, value, metadata)
end
end
defp handle_existing_sync(
state,
sync_id,
key,
value,
metadata,
existing_entry
) do
case resolve_conflict(metadata, existing_entry.metadata) do
:keep_existing ->
{:ok, state}
:use_new ->
new_state = update_sync_entry(state, sync_id, key, value, metadata)
{:ok, new_state}
:conflict ->
new_state = increment_conflict_count(state, sync_id)
{:error, :conflict, new_state}
end
end
defp handle_new_sync(state, sync_id, key, value, metadata) do
new_state = update_sync_entry(state, sync_id, key, value, metadata)
{:ok, new_state}
end
defp resolve_conflict(new_metadata, existing_metadata) do
case {new_metadata.consistency, existing_metadata.consistency} do
{:strong, :strong} ->
case new_metadata.version > existing_metadata.version do
true -> :use_new
false -> :keep_existing
end
{:strong, _} ->
:use_new
{_, :strong} ->
:keep_existing
{_, _} ->
case new_metadata.version > existing_metadata.version do
true -> :use_new
false -> :conflict
end
end
end
defp get_sync_entry(state, sync_id, key) do
case Map.get(state.syncs, sync_id) do
nil ->
{:error, :not_found}
sync_data ->
case Map.fetch(sync_data, key) do
{:ok, entry} -> {:ok, entry}
:error -> {:error, :not_found}
end
end
end
defp update_sync_entry(state, sync_id, key, value, metadata) do
entry = %{key: key, value: value, metadata: metadata}
sync_data = Map.get(state.syncs, sync_id, %{})
new_sync_data = Map.put(sync_data, key, entry)
new_syncs = Map.put(state.syncs, sync_id, new_sync_data)
new_stats = update_sync_stats(state.stats, sync_id, metadata.consistency)
%{state | syncs: new_syncs, stats: new_stats}
end
defp delete_sync_entry(state, sync_id, key) do
case Map.get(state.syncs, sync_id) do
nil ->
state
sync_data ->
new_sync_data = Map.delete(sync_data, key)
new_syncs = Map.put(state.syncs, sync_id, new_sync_data)
%{state | syncs: new_syncs}
end
end
defp clear_sync_entries(state, sync_id) do
new_syncs = Map.delete(state.syncs, sync_id)
new_stats = Map.delete(state.stats, sync_id)
%{state | syncs: new_syncs, stats: new_stats}
end
defp update_sync_stats(stats, sync_id, consistency) do
sync_stats =
Map.get(stats, sync_id, %{
sync_count: 0,
conflict_count: 0,
last_sync: 0,
consistency_levels: %{strong: 0, eventual: 0, causal: 0}
})
new_sync_stats = %{
sync_stats
| sync_count: sync_stats.sync_count + 1,
last_sync: System.monotonic_time(),
consistency_levels: Map.update(sync_stats.consistency_levels, consistency, 1, &(&1 + 1))
}
Map.put(stats, sync_id, new_sync_stats)
end
defp increment_conflict_count(state, sync_id) do
sync_stats = Map.get(state.stats, sync_id)
new_sync_stats = %{
sync_stats
| conflict_count: sync_stats.conflict_count + 1
}
%{state | stats: Map.put(state.stats, sync_id, new_sync_stats)}
end
@doc """
Gets the current monotonic time in the specified unit.
"""
@spec monotonic_time(:millisecond | :microsecond | :nanosecond) :: integer()
def monotonic_time(unit) do
System.monotonic_time(unit)
end
@doc """
Gets the current system time in the specified unit.
"""
@spec system_time(:millisecond | :microsecond | :nanosecond) :: integer()
def system_time(unit) do
System.system_time(unit)
end
end