Updated database schema, and did some organizing

main
noah metz 2024-01-26 21:06:24 -07:00
parent ca5862ebb6
commit 4e61172f6d
5 changed files with 72 additions and 32 deletions

@ -23,7 +23,7 @@
2},
{<<"sqlite3">>,
{git,"https://github.com/Xelynega/erlang-sqlite3",
{ref,"5703b047a8ff66450a4de13af37cee10dfe656d5"}},
{ref,"396e8e4161c003503a80ea53395278323ca4093d"}},
0}]}.
[
{pkg_hash,[

@ -11,8 +11,6 @@
-export([init/1]).
-define(SERVER, ?MODULE).
start_link() ->
supervisor:start_link(?MODULE, []).
@ -29,6 +27,9 @@ start_link() ->
init(_) ->
SupFlags = #{strategy => rest_for_one},
ChildSpecs = [
#{id => schedule,
start => {schedule, start_link, []}
},
#{id => mqtt,
start => {mqtt, start_link, []}
},

@ -2,7 +2,8 @@
-behaviour(gen_server).
-export([start_link/1, init/1, handle_cast/2, update_config/2, handle_call/3]).
-export([start_link/1, init/1, handle_cast/2, handle_call/3, handle_info/2]).
-export([update_config/2]).
-record(event_config, {database_file = none, skills_fields = [], practice_fields = [], divisions = []}).
@ -73,7 +74,8 @@ parse_event_config(ConfigJSON, State) ->
EliminationFields = [binary_to_list(X) || X <- maps:get(<<"elimination_fields">>, Config)],
Fields = sets:to_list(sets:from_list(lists:append([DivisionFields, SkillsFields, PracticeFields, EliminationFields]))),
FieldStates = update_fields(Fields, State#event_state.fields),
FieldStates = add_fields(Fields, State#event_state.fields),
State#event_state{config = #event_config{
database_file = binary_to_list(maps:get(<<"database">>, Config)),
skills_fields = SkillsFields,
@ -90,24 +92,42 @@ get_field_state(Name, [{FieldName, FieldState} | FieldStates]) ->
true -> get_field_state(Name, FieldStates)
end.
start_next_event(State) ->
State.
update_fields(Fields, FieldStates) ->
GetState = fun(Name) -> get_field_state(Name, FieldStates) end,
lists:zip(Fields, lists:map(GetState, Fields)).
handle_call({event_config, ConfigJSON}, _, State) ->
NewState = parse_event_config(ConfigJSON, State),
add_fields(Fields, FieldStates) ->
[{Name, get_field_state(Name, FieldStates)} || Name <- Fields].
update_field({Name, free}, State) ->
% TODO: Calculate which event should be running on this field
% Priority List Logic:
% 1) if testing field, assign testing event
% 2) if skills field, assign skills event
% 3) if division field
% 0) in general, assign match if: (state != done && (num % num_fields) == field_index)
% 1) Get practice match from DB
% 2) Get qualification match from DB
% 2) Get lists of elimination matches from DB, assign(and generate if necessary) first where:
FieldState = free,
{{Name, FieldState}, State};
update_field({Name, FieldState}, State) -> {{Name, FieldState}, State}.
update_events(State) ->
% TODO: calculate which events should be running
% TODO: stop events on fields that no longer exist
% TODO: stop events on fields that are running the wrong event
% TODO: start every event that should be running given the config
{NewFields, NewState} = lists:mapfoldl(fun update_field/2, State, State#event_state.fields),
NewState#event_state{fields = NewFields}.
handle_call({event_config, ConfigJSON}, _, State) ->
NewState = parse_event_config(ConfigJSON, State),
State#event_state.owner ! {new_event_config, NewState#event_state.config},
{reply, ok, NewState};
handle_call(event_done, _From, State) ->
{noreply, start_next_event(State)}.
handle_call(_, _, State) ->
{noreply, update_events(State)}.
handle_cast(_, State) ->
{noreply, State}.
{noreply, update_events(State)}.
handle_info(_, State) ->
{noreply, update_events(State)}.

@ -8,7 +8,6 @@ start_link() ->
supervisor:start_link(?MODULE, []).
init([]) ->
io:fwrite("SUP_INIT~n"),
SupFlags = #{strategy => one_for_one,
intensity => 0,
period => 1},

@ -4,7 +4,7 @@
-export([start_link/0, init/1, handle_cast/2, handle_call/3]).
-export([remove/2, generate/3, write/6, pick_teams/2, pick_matches/1, hash_teams_list/1, init_db/1]).
-export([init_db/2, generate/2]).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
@ -13,12 +13,6 @@ init([]) ->
rand:seed(default),
{ok, []}.
handle_cast(_, State) ->
{noreply, State}.
remove(List, Index) ->
[X || {I, X} <- lists:enumerate(0, List), I /= Index].
duplicate(1, Original, Constructed) -> lists:append(Original, Constructed);
duplicate(N, Original, Constructed) -> duplicate(N-1, Original, lists:append(Original, Constructed)).
@ -46,10 +40,17 @@ fill_padding_schedule(Matches, Teams) ->
hash_teams_list(Teams) ->
[ Y || <<X:4>> <= crypto:hash(sha, Teams), Y <- integer_to_list(X,16)].
generate(Process, {DatabaseFile, Division, Round, Seed, Matches, Teams}) -> gen_server:call(Process, {new_schedule, DatabaseFile, Division, Round, Seed, Matches, Teams}).
init_db(Process, DatabaseFile) -> gen_server:call(Process, {init_db, DatabaseFile}).
init_db(File) ->
IsFile = filelib:is_regular(File),
if IsFile -> file:delete(File) end,
if IsFile -> file:delete(File);
true -> true end,
{ok, Database} = sqlite3:open(anonymous, [{file, File}]),
ok = sqlite3:create_table(Database, teams, [{name, text, [not_null]},
{inspection, blob}],
[{primary_key, [name]}]),
ok = sqlite3:create_table(Database, matches, [{division, integer, [not_null]},
{round, text, [not_null]},
{size, integer, [not_null]},
@ -60,7 +61,12 @@ init_db(File) ->
{red_2, text, [not_null]},
{blue_1, text, [not_null]},
{blue_2, text, [not_null]}],
[{primary_key, [division, round, size, teams, number, seed]}]),
[{primary_key, [division, round, size, teams, number, seed]},
{check, "round IN ('practice', 'qualification', 'elimination')"},
{foreign_key, {[blue_1], teams, [name], "ON DELETE RESTRICT"}},
{foreign_key, {[blue_2], teams, [name], "ON DELETE RESTRICT"}},
{foreign_key, {[red_1], teams, [name], "ON DELETE RESTRICT"}},
{foreign_key, {[red_2], teams, [name], "ON DELETE RESTRICT"}}]),
ok = sqlite3:create_table(Database, match_scores, [{division, integer, [not_null]},
{round, text, [not_null]},
{size, integer, [not_null]},
@ -81,10 +87,19 @@ init_db(File) ->
{state, blob, [not_null]}],
[{primary_key, [division, round, size, teams, number, seed, time]},
{foreign_key, {[division, round, size, teams, number, seed], matches, [division, round, size, teams, number, seed], "ON DELETE CASCADE"}}]),
% TODO: finals
% ok = sqlite3:create_table(Database, finals, [], [{primary_key, []}]),
ok = sqlite3:create_table(Database, skills_scores, [{team, text, [not_null]},
{type, text, [not_null]},
{attempt, integer, [not_null]},
{score, blob, [not_null]}],
[{primary_key, [team, type, attempt]},
{foreign_key, {[team], teams, [name], "ON DELETE CASCADE"}}]),
sqlite3:close(Database).
write(DatabaseFile, Division, Round, Seed, Matches, Teams) ->
MatchList = generate(Seed, Matches, Teams),
MatchList = create_schedule(Seed, Matches, Teams),
Schedule = [[{division, Division},
{round, atom_to_list(Round)},
{size, Matches},
@ -96,11 +111,12 @@ write(DatabaseFile, Division, Round, Seed, Matches, Teams) ->
{red_1, R1},
{red_2, R2}] || {N, [B1, B2, R1, R2]} <- lists:enumerate(MatchList)],
{ok, Database} = sqlite3:open(anonymous, [{file, DatabaseFile}]),
[ok | _] = sqlite3:write_many(Database, matches, Schedule),
[ok | Resp] = sqlite3:write_many(Database, matches, Schedule),
ok = lists:last(Resp),
ok = sqlite3:close(Database).
generate(Seed, Matches, Teams) ->
create_schedule(Seed, Matches, Teams) ->
TeamsRepeated = duplicate(Matches, Teams),
TeamsPadded = TeamsRepeated ++ lists:duplicate(padding(length(Teams)*Matches), padding),
rand:seed(default, Seed),
@ -123,5 +139,9 @@ pick_matches(Teams, Matches) ->
pick_matches(Teams) -> pick_matches(Teams, []).
handle_call(Req, _, State) ->
{reply, Req, State}.
handle_cast(_, State) -> {noreply, State}.
handle_call({init_db, DatabaseFile}, _, State) ->
{reply, init_db(DatabaseFile), State};
handle_call({new_schedule, DatabaseFile, Division, Round, Seed, Matches, Teams}, _, State) ->
{reply, write(DatabaseFile, Division, Round, Seed, Matches, Teams), State}.