diff --git a/src/ggs_gamevm.erl b/src/ggs_gamevm.erl index 87817fc..c6703c6 100644 --- a/src/ggs_gamevm.erl +++ b/src/ggs_gamevm.erl @@ -4,31 +4,33 @@ -module(ggs_gamevm). -behaviour(gen_server). -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --record(state, { port, table, global } ). +-include_lib("erlv8/include/erlv8.hrl"). %% API --export([start_link/1, define/3, player_command/5, stop/1, call_js/2]). +-export([start_link/1,stop/1]). +-export([define/3, player_command/5, call_js/2]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2]). +-export([code_change/3, handle_info/2, terminate/2]). + +-record(state, { vm, global, table } ). %% ---------------------------------------------------------------------- % API implementation + %% @doc Create a new VM process. The process ID is returned and can be used %% with for example the define method of this module. -start_link(Table) -> - %erlv8_vm:start(), %c NEW - application:start(erlv8), - %erlang_js:start(), %c OLD %% @TODO: should only be done once - {ok, Pid} = gen_server:start_link(?MODULE, [Table], []), - Pid. +start_link(Table) -> + {ok, AccessVM} = gen_server:start_link(?MODULE, [Table], []), + expose_to_js(AccessVM), + AccessVM. %% @doc Define some new code on the specified VM, returns the atom ok. -define(GameVM, Key, SourceCode) -> - gen_server:cast(GameVM, {define, Key, SourceCode}). %c Mod +define(AccessVM, Key, SourceCode) -> + gen_server:cast(AccessVM, {define, Key, SourceCode}). %% @doc Execute a player command on the specified VM. This function is %% asynchronous, and returns ok. @@ -37,82 +39,206 @@ define(GameVM, Key, SourceCode) -> %% Player = the player running the command %% Command = a game command to run %% Args = arguments for the Command parameter -player_command(GameVM, Key, Player, Command, Args) -> - gen_server:cast(GameVM, {player_command, Key, Player, Command, Args}). +player_command(accessVM, Key, Player, Command, Args) -> + gen_server:cast(accessVM, {player_command, Key, Player, Command, Args}). %% @private % only for tests -call_js(GameVM, SourceCode) -> - gen_server:call(GameVM, {eval, SourceCode}). - +call_js(AccessVM, SourceCode) -> + gen_server:call(AccessVM, {eval, SourceCode}). + % @doc stops the gamevm process -stop(GameVM) -> - gen_server:cast(GameVM, stop). +stop(AccessVM) -> + gen_server:cast(AccessVM, stop). +expose_to_js(AccessVM) -> + expose_db_set_item(AccessVM), + expose_db_remove_item(AccessVM), + expose_db_clear1(AccessVM), + expose_db_clear2(AccessVM), + expose_db_get_item(AccessVM), + expose_db_length(AccessVM), + expose_db_key(AccessVM), + expose_table_send_command(AccessVM). -%% ---------------------------------------------------------------------- - -%% @private -init([Table]) -> +%run() -> +% VM = fetch("vm"), +% JSApp = fetch("jsapp"), +% erlv8_vm:run(VM, JSApp), +% erlv8_vm:run(VM,"js_print('Blah!')"), +% erlv8_vm:run(VM,"setItem('Token', 'World', 'Key', 'Value')"), +% erlv8_vm:run(VM,"setItem('Token', 'World', 'Key2', 'Value2')"), +% Length = ggs_db:length("Token", "World"), +% Value = ggs_db:getItem("Token", "World", "Key"), +% io:format("Length: ~B~n",[Length]), +% io:format("Value: ~s~n", [Value]). + +init([Table]) -> process_flag(trap_exit, true), - %{ok, Port} = js_driver:new(), c Old - {ok, Port} = erlv8_vm:start(), %c New - Global = erlv8_vm:global(Port), %c New - {ok, JSAPISourceCode} = file:read_file("src/ggs_api.js"), - %ok = js:define(Port, JSAPISourceCode), c Old - Global:set_value("src", JSAPISourceCode), %c New - InitGGSJSString = "var GGS = new _GGS(" ++ Table ++ ");", - %ok = js:define(Port, list_to_binary(InitGGSJSString)), c Old - Global:set_value("ggs", InitGGSJSString), %c New - {ok, #state { port = Port, table = Table, global = Global }}. %c Mod + application:start(erlv8), % Start erlv8 + {ok, VM} = erlv8_vm:start(), % Create a JavaScript vm + Global = erlv8_vm:global(VM), % Retrieve JS global + ggs_db:init(), % Initialize the database + {ok, #state { vm = VM, global = Global, table = Table }}. + +%%% Expose ggs_db + +expose_db_set_item(AccessVM) -> + gen_server:cast(AccessVM, set_item). + +expose_db_remove_item(AccessVM) -> + gen_server:cast(AccessVM, remove_item). + +expose_db_clear1(AccessVM) -> + gen_server:cast(AccessVM, clear1). + +expose_db_clear2(AccessVM) -> + gen_server:cast(AccessVM, clear2). + +expose_db_get_item(AccessVM) -> + gen_server:cast(AccessVM, get_item). + +expose_db_length(AccessVM) -> + gen_server:cast(AccessVM, length). + +expose_db_key(AccessVM) -> + gen_server:cast(AccessVM, key). + + +%%% Expose ggs_table + +expose_table_send_command(AccessVM) -> + gen_server:cast(AccessVM, send_command). + +%% TODO: expose_table_send_command_to_all() + +%% Makes a function invokable from JavaScript as: "erlang.Name(Args)" +%expose_fun(Global, Name, Fun) -> +% Global:set_value("erlang", erlv8_object:new([{Name, +% fun (#erlv8_fun_invocation{}, +% [Args]) -> +% Fun(Args) end}])). + +expose_fun(Global, Name, Fun, 1) -> + Global:set_value("GGS.localStorage", erlv8_object:new([{Name, + fun (#erlv8_fun_invocation{}, + [Arg]) -> + Fun(Arg) end}])); + +expose_fun(Global, Name, Fun, 2) -> + Global:set_value("GGS.localStorage", erlv8_object:new([{Name, + fun (#erlv8_fun_invocation{}, + [Arg1,Arg2]) -> + Fun(Arg1,Arg2) end}])); + + +expose_fun(Global, Name, Fun, 3) -> + Global:set_value("GGS.localStorage", erlv8_object:new([{Name, + fun (#erlv8_fun_invocation{}, + [Arg1,Arg2,Arg3]) -> + Fun(Arg1,Arg2,Arg3) end}])); + +expose_fun(Global, Name, Fun, 4) -> + Global:set_value("GGS.localStorage", erlv8_object:new([{Name, + fun (#erlv8_fun_invocation{}, + [Arg1,Arg2,Arg3,Arg4]) -> + Fun(Arg1,Arg2,Arg3,Arg4) + end}])). + + +%% Retrieve a JavaScript file from hard drive and return it +%read_js_file(Filename) -> +% {ok, JSApp} = file:read_file(Filename), +% erlang:binary_to_list(JSApp). + + +%print(Args) -> +% io:format("~s~n", [Args]). + %% private % only needed for the tests -handle_call({eval, SourceCode}, _From, #state { port = Port } = State) -> - %{ok, Ret} = js:eval(Port, list_to_binary(SourceCode)), c Old - {ok, Ret} = erlv8_vm:run(Port, SourceCode), %c New +handle_call({eval, SourceCode}, _From, #state { vm = VM } = State) -> + {ok, Ret} = erlv8_vm:run(VM, SourceCode), {reply, Ret, State}. -%% @private -handle_cast({define, Key, SourceCode}, #state { port = Port, table = Table, global = Global } = State) -> %c Mod - %Ret = js:define(Port, list_to_binary(SourceCode)), %c old +handle_cast({define, Key, SourceCode}, + #state { table = Table, global = Global } = State) -> Global:set_value(Key, SourceCode), - ggs_table:notify_all_players(Table, {"defined", "ok"}), %c ok - {noreply, State}; %c New - %case Ret of %c Old - % ok -> - % ggs_table:notify_all_players(Table, {"defined", "ok"}), - % {noreply, State}; - % Other -> - % ggs_table:notify_all_players(Table, {"defined", "error " ++ Other}), - % {noreply, State} - %end; -handle_cast({player_command, Key, Player, Command, Args}, #state { port = Port, global = Global } = State) -> - %Js = list_to_binary("playerCommand(new Player('" ++ Player ++ "'), '" ++ js_escape(Command) ++ "', '" ++ js_escape(Args) ++ "');"), %c Old - Js = "playerCommand(new Player('" ++ Player ++ "'), '" ++ js_escape(Command) ++ "', '" ++ js_escape(Args) ++ "');", %c New - %%js_driver:define_js(Port, Js), %c old - Global:set_value(Key, Js), %c new - erlang:display(binary_to_list(Js)), + ggs_table:notify_all_players(Table, {"defined", "ok"}), + {noreply, State}; + +handle_cast({player_command, Key, Player, Command, Args}, + #state { global = Global } = State) -> + Js = "playerCommand(new Player('" ++ Player ++ "'), '" ++ + js_escape(Command) ++ "', '" ++ js_escape(Args) ++ "');", + Global:set_value(Key, Js), + erlang:display(binary_to_list(Js)), {noreply, State}; -handle_cast(stop, State) -> + +handle_cast(stop, #state { vm = VM } = State) -> + erlv8_vm:stop(VM), {stop, normal, State}; + +handle_cast(set_item, #state { global = Global } = State) -> + Fun = (fun(GameToken,Ns,Key,Value) -> ggs_db:setItem(GameToken,Ns,Key,Value) end), + expose_fun( Global, "setItem", Fun, 4), + {noreply, State}; + +handle_cast(remove_item, #state { global = Global } = State) -> + Fun = (fun(GameToken,Ns,Key) -> ggs_db:removeItem(GameToken,Ns,Key) end), + expose_fun( Global, "removeItem", Fun, 3), + {noreply, State}; + +handle_cast(clear1, #state { global = Global } = State) -> + Fun = (fun(GameToken) -> ggs_db:clear(GameToken) end), + expose_fun( Global, "clear", Fun, 1), + {noreply, State}; + +handle_cast(clear2, #state { global = Global } = State) -> + Fun = (fun(GameToken,Ns) -> ggs_db:clear(GameToken,Ns) end), + expose_fun( Global, "clear", Fun, 2), + {noreply, State}; + +handle_cast(get_item, #state { global = Global } = State) -> + Fun = (fun(GameToken,Ns,Key) -> ggs_db:getItem(GameToken,Ns,Key) end), + expose_fun( Global, "getItem", Fun, 3), + {noreply, State}; + +handle_cast(length, #state { global = Global } = State) -> + Fun = (fun(GameToken,Ns) -> ggs_db:length(GameToken,Ns) end), + expose_fun( Global, "length", Fun, 2), + {noreply, State}; + +handle_cast(key, #state { global = Global } = State) -> + Fun = (fun(GameToken,Ns,Position) -> ggs_db:key(GameToken,Ns,Position) end), + expose_fun( Global, "key", Fun, 3), + {noreply, State}; + +handle_cast(send_command, #state { global = Global } = State) -> + Fun = (fun(GameToken,PlayerToken,Message) -> ggs_table:send_command(GameToken,PlayerToken,Message) end), + expose_fun( Global, "sendCommand", Fun, 3), + {noreply, State}; + handle_cast(Msg, S) -> error_logger:error_report([unknown_msg, Msg]), {noreply, S}. +%% @private +code_change(_, State, _) -> + {ok, State}. + %% @private handle_info(Msg, S) -> error_logger:error_report([unknown_msg, Msg]), {noreply, S}. + %% @private -terminate(_Reason, _State) -> +terminate(_, _) -> ok. %% @private -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - +%% This function is probably important. Do not touch! js_escape(S) -> - lists:flatmap(fun($\') -> [$\\, $\']; (X) -> [X] end, S). - + lists:flatmap(fun($\') -> [$\\, $\']; (X) -> [X] end, S).