diff --git a/rebar.lock b/rebar.lock index 3338be3..0654497 100644 --- a/rebar.lock +++ b/rebar.lock @@ -23,7 +23,7 @@ 2}, {<<"sqlite3">>, {git,"https://github.com/Xelynega/erlang-sqlite3", - {ref,"5703b047a8ff66450a4de13af37cee10dfe656d5"}}, + {ref,"396e8e4161c003503a80ea53395278323ca4093d"}}, 0}]}. [ {pkg_hash,[ diff --git a/src/erlk_sup.erl b/src/erlk_sup.erl index 4bbf005..c446f4a 100644 --- a/src/erlk_sup.erl +++ b/src/erlk_sup.erl @@ -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, []} }, diff --git a/src/event_mgr.erl b/src/event_mgr.erl index df0e331..3ea2cdc 100644 --- a/src/event_mgr.erl +++ b/src/event_mgr.erl @@ -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)}. diff --git a/src/event_sup.erl b/src/event_sup.erl index fc0189a..7efae09 100644 --- a/src/event_sup.erl +++ b/src/event_sup.erl @@ -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}, diff --git a/src/schedule.erl b/src/schedule.erl index ffba69b..2024830 100644 --- a/src/schedule.erl +++ b/src/schedule.erl @@ -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 || <> <= 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}.