# HG changeset patch # User Simon Heath # Date 1655844696 14400 # Tue Jun 21 16:51:36 2022 -0400 # Node ID eb54c6b5c82d75a305c6be43f63691ca47c0f0f4 # Parent fc1f5fff318ceb634b284c6bddb855166545c366 EDoc isn't the worst! Huzzah. diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -71,9 +71,10 @@ # Tools - * Use `dialyzer` for linting + * Build tool: https://github.com/erlang/rebar3 and https://rebar3.org/docs/ + * Unit tests: etest + * Linting: `dialyzer` * https://github.com/rusterlium/rustler -- Rust NIF's - * Build tool: https://github.com/erlang/rebar3 and https://rebar3.org/docs/ * Logging: https://hex.pm/packages/lager or https://hex.pm/packages/hut or https://hex.pm/packages/slogger diff --git a/apps/goatherd/src/gh_time.erl b/apps/goatherd/src/gh_time.erl --- a/apps/goatherd/src/gh_time.erl +++ b/apps/goatherd/src/gh_time.erl @@ -1,47 +1,49 @@ -% Timing functions. -% See we need to have a single clock that everything uses, but it has to -% be able to be simulated as well. So this clock is going to stop and -% start, potentially run at a different rate than realtime, etc. It -% will NOT run backwards, quite, but there needs to be a `reset` message -% that informs clients "the clock has jumped a significant amount, throw -% away your state and start over", which *could* involve moving -% backwards. -% -% We are going to use the Erlang Term Storage to store a "global -% mutable" value and have a little node to update it continuously. -% Then a client can either get the latest time, or have a process -% running that will keep track of the last time it was asked for -% time and check for jumps, reset's, etc. -% -% Time is Hard. See -% https://www.erlang.org/doc/apps/erts/time_correction.html -% for some of the details around it. -% -% The timer runs at a fixed rate. For now we will call it 100 Hz, so -% this is the maximum resolution it can manage. It uses Erlang -% monotonic time as it stime source, so it may warp or skew, but only -% forwards. Thus we assume we have a Good OS time source exists before -% we start anything and that it won't skew around too much. It also -% ONLY works on the local node; clients on other nodes will not get any -% messages from this. Timing across distributed systems is NOT -% something I am going to try to do right now. -% -% It sends out time from some arbitrary point in the past, in -% milliseconds. The user may not assume that they know what the rate or -% resolution of the timer is. Just that, as far as the robot is -% concerned, each message is "exact enough". +%% @doc Timing functions. +%% +%% See we need to have a single clock that everything uses, but it has to +%% be able to be simulated as well. So this clock is going to stop and +%% start, potentially run at a different rate than realtime, etc. It +%% will NOT run backwards, quite, but there needs to be a `reset` message +%% that informs clients "the clock has jumped a significant amount, throw +%% away your state and start over", which *could* involve moving +%% backwards. +%% +%% We are going to use the Erlang Term Storage to store a "global +%% mutable" value and have a little node to update it continuously. +%% Then a client can either get the latest time, or have a process +%% running that will keep track of the last time it was asked for +%% time and check for jumps, reset's, etc. +%% +%% Time is Hard. See +%% [https://www.erlang.org/doc/apps/erts/time_correction.html] +%% for some of the details around it. +%% +%% The timer runs at a fixed rate. For now we will call it 100 Hz, so +%% this is the maximum resolution it can manage. It uses Erlang +%% monotonic time as it stime source, so it may warp or skew, but only +%% forwards. Thus we assume we have a Good OS time source exists before +%% we start anything and that it won't skew around too much. It also +%% ONLY works on the local node; clients on other nodes will not get any +%% messages from this. Timing across distributed systems is NOT +%% something I am going to try to do right now. +%% +%% It sends out time from some arbitrary point in the past, in +%% milliseconds. The user may not assume that they know what the rate or +%% resolution of the timer is. Just that, as far as the robot is +%% concerned, each message is "exact enough". -module(gh_time). -export([init/0, start/0, start_link/0, raw_time/0, start_timeserver/0, timeserver/1, timeserver_time/1]). -% Start the timer service +%% @doc Start the timer service start() -> spawn_link(?MODULE, init, []). -% Start the timer service +%% @doc Start the timer service start_link() -> spawn_link(?MODULE, init, []). +%% @private init() -> register(gh_time, self()), Now = erlang:monotonic_time(milli_seconds), @@ -50,24 +52,37 @@ ets:insert(Table, {now, Now}), loop(Table, Now). -% Call this function to get the raw time. -% Users should do start_timeserver instead. +%% @private +%% @doc Call this function to get the raw time. +%% Users should do start_timeserver instead. raw_time() -> [{now, Now}] = ets:lookup(gh_time, now), Now. -% Actually it should start a time tracker thingy for the user. -% It should run one per user process and keep track of its local -% time info, so it can report dt and whether the time has jumped, -% reset, or something else. -% -% TODO: Start loop if necessary??? -% Link this to the loop so we restart these if the loop screws up? -% Ponder more later. +%% @doc Start a time tracker thingy for the user +%% +%% It should run one per user process and keep track of its local +%% time info, so it can report dt and whether the time has jumped, +%% reset, or something else. +%% +%% @returns The PID of the time server process +%% +%% TODO: Start loop if necessary??? +%% Link this to the loop so we restart these if the loop screws up? +%% Or at least detect it somehow, since raw_time() should be monotonic +%% and thus never return the exact same time twice? +%% Ponder more later. +-spec start_timeserver() -> pid(). start_timeserver() -> Now = raw_time(), spawn(?MODULE, timeserver, [Now]). +%% @doc Get the time from the given time server. Units are +%% milliseconds. Start time is arbitrary. +%% +%% @returns {status, absolute time, dt since last query to the +%% time server. +-spec timeserver_time(pid()) -> {atom(), integer(), integer()}. timeserver_time(Timeserver) -> Timeserver ! self(), receive @@ -75,17 +90,19 @@ % TODO: Timeout? end. -% This is the main loop that uses the Erlang Term Storage to -% store what the last known time is. ETS is node-local, so -% time is node-local. -% -% Time is monotonic. FOR NOW we do not do anything -% Time 0 point is arbitrary??? -% Units??? -- selectable, milliseconds for now. -% See https://www.erlang.org/doc/apps/erts/time_correction.html -% -% To consider later: alternate time sources such as a specific -% chrony server, GPS time source, etc. +%% @private +%% +%% @doc This is the main loop that uses the Erlang Term Storage to +%% store what the last known time is. ETS is node-local, so +%% time is node-local. +%% +%% Time is monotonic. FOR NOW we do not do anything +%% Time 0 point is arbitrary??? +%% Units??? -- selectable, milliseconds for now. +%% See https://www.erlang.org/doc/apps/erts/time_correction.html +%% +%% To consider later: alternate time sources such as a specific +%% chrony server, GPS time source, etc. loop(Table, LastUpdate) -> % Milliseconds per tick Ms = 10, @@ -107,13 +124,17 @@ loop(Table, LastUpdate) end. +%% @private +%% +%% @doc Main loop of the client-side time server. timeserver(LastUpdate) -> % Hmmm, what should the max error be? % Basically we want to warn the client if % it hasn't called this frequently enough. % Maybe that's the client's problem? % Hmmmm. This is intended to keep track of - % it *for* the client. Maybe have a hz param. + % it *for* the client. Maybe have a hz param + % the client provides. MaxError = 10, Now = raw_time(), Dt = Now - LastUpdate,