ggs_gamevm now with v8 and erlang invocations from js. both localstorage and world is callable using GGS.localStorage. TODO modify world using GGS.world instead off GGS.localStorage. TODO lots of testing.
This commit is contained in:
parent
0b649c35ac
commit
629c1f6821
1 changed files with 190 additions and 64 deletions
|
@ -4,31 +4,33 @@
|
||||||
-module(ggs_gamevm).
|
-module(ggs_gamevm).
|
||||||
-behaviour(gen_server).
|
-behaviour(gen_server).
|
||||||
|
|
||||||
%% gen_server callbacks
|
-include_lib("erlv8/include/erlv8.hrl").
|
||||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
|
||||||
terminate/2, code_change/3]).
|
|
||||||
|
|
||||||
-record(state, { port, table, global } ).
|
|
||||||
|
|
||||||
%% API
|
%% 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
|
% API implementation
|
||||||
|
|
||||||
|
|
||||||
%% @doc Create a new VM process. The process ID is returned and can be used
|
%% @doc Create a new VM process. The process ID is returned and can be used
|
||||||
%% with for example the define method of this module.
|
%% with for example the define method of this module.
|
||||||
start_link(Table) ->
|
start_link(Table) ->
|
||||||
%erlv8_vm:start(), %c NEW
|
{ok, AccessVM} = gen_server:start_link(?MODULE, [Table], []),
|
||||||
application:start(erlv8),
|
expose_to_js(AccessVM),
|
||||||
%erlang_js:start(), %c OLD %% @TODO: should only be done once
|
AccessVM.
|
||||||
{ok, Pid} = gen_server:start_link(?MODULE, [Table], []),
|
|
||||||
Pid.
|
|
||||||
|
|
||||||
%% @doc Define some new code on the specified VM, returns the atom ok.
|
%% @doc Define some new code on the specified VM, returns the atom ok.
|
||||||
define(GameVM, Key, SourceCode) ->
|
define(AccessVM, Key, SourceCode) ->
|
||||||
gen_server:cast(GameVM, {define, Key, SourceCode}). %c Mod
|
gen_server:cast(AccessVM, {define, Key, SourceCode}).
|
||||||
|
|
||||||
%% @doc Execute a player command on the specified VM. This function is
|
%% @doc Execute a player command on the specified VM. This function is
|
||||||
%% asynchronous, and returns ok.
|
%% asynchronous, and returns ok.
|
||||||
|
@ -37,82 +39,206 @@ define(GameVM, Key, SourceCode) ->
|
||||||
%% Player = the player running the command
|
%% Player = the player running the command
|
||||||
%% Command = a game command to run
|
%% Command = a game command to run
|
||||||
%% Args = arguments for the Command parameter
|
%% Args = arguments for the Command parameter
|
||||||
player_command(GameVM, Key, Player, Command, Args) ->
|
player_command(accessVM, Key, Player, Command, Args) ->
|
||||||
gen_server:cast(GameVM, {player_command, Key, Player, Command, Args}).
|
gen_server:cast(accessVM, {player_command, Key, Player, Command, Args}).
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
% only for tests
|
% only for tests
|
||||||
call_js(GameVM, SourceCode) ->
|
call_js(AccessVM, SourceCode) ->
|
||||||
gen_server:call(GameVM, {eval, SourceCode}).
|
gen_server:call(AccessVM, {eval, SourceCode}).
|
||||||
|
|
||||||
% @doc stops the gamevm process
|
% @doc stops the gamevm process
|
||||||
stop(GameVM) ->
|
stop(AccessVM) ->
|
||||||
gen_server:cast(GameVM, stop).
|
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).
|
||||||
|
|
||||||
%% ----------------------------------------------------------------------
|
%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]).
|
||||||
|
|
||||||
%% @private
|
|
||||||
init([Table]) ->
|
init([Table]) ->
|
||||||
process_flag(trap_exit, true),
|
process_flag(trap_exit, true),
|
||||||
%{ok, Port} = js_driver:new(), c Old
|
application:start(erlv8), % Start erlv8
|
||||||
{ok, Port} = erlv8_vm:start(), %c New
|
{ok, VM} = erlv8_vm:start(), % Create a JavaScript vm
|
||||||
Global = erlv8_vm:global(Port), %c New
|
Global = erlv8_vm:global(VM), % Retrieve JS global
|
||||||
{ok, JSAPISourceCode} = file:read_file("src/ggs_api.js"),
|
ggs_db:init(), % Initialize the database
|
||||||
%ok = js:define(Port, JSAPISourceCode), c Old
|
{ok, #state { vm = VM, global = Global, table = Table }}.
|
||||||
Global:set_value("src", JSAPISourceCode), %c New
|
|
||||||
InitGGSJSString = "var GGS = new _GGS(" ++ Table ++ ");",
|
%%% Expose ggs_db
|
||||||
%ok = js:define(Port, list_to_binary(InitGGSJSString)), c Old
|
|
||||||
Global:set_value("ggs", InitGGSJSString), %c New
|
expose_db_set_item(AccessVM) ->
|
||||||
{ok, #state { port = Port, table = Table, global = Global }}. %c Mod
|
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
|
%% private
|
||||||
% only needed for the tests
|
% only needed for the tests
|
||||||
handle_call({eval, SourceCode}, _From, #state { port = Port } = State) ->
|
handle_call({eval, SourceCode}, _From, #state { vm = VM } = State) ->
|
||||||
%{ok, Ret} = js:eval(Port, list_to_binary(SourceCode)), c Old
|
{ok, Ret} = erlv8_vm:run(VM, SourceCode),
|
||||||
{ok, Ret} = erlv8_vm:run(Port, SourceCode), %c New
|
|
||||||
{reply, Ret, State}.
|
{reply, Ret, State}.
|
||||||
|
|
||||||
%% @private
|
handle_cast({define, Key, SourceCode},
|
||||||
handle_cast({define, Key, SourceCode}, #state { port = Port, table = Table, global = Global } = State) -> %c Mod
|
#state { table = Table, global = Global } = State) ->
|
||||||
%Ret = js:define(Port, list_to_binary(SourceCode)), %c old
|
|
||||||
Global:set_value(Key, SourceCode),
|
Global:set_value(Key, SourceCode),
|
||||||
ggs_table:notify_all_players(Table, {"defined", "ok"}), %c ok
|
ggs_table:notify_all_players(Table, {"defined", "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)),
|
|
||||||
{noreply, State};
|
{noreply, State};
|
||||||
handle_cast(stop, 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 { vm = VM } = State) ->
|
||||||
|
erlv8_vm:stop(VM),
|
||||||
{stop, normal, State};
|
{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) ->
|
handle_cast(Msg, S) ->
|
||||||
error_logger:error_report([unknown_msg, Msg]),
|
error_logger:error_report([unknown_msg, Msg]),
|
||||||
{noreply, S}.
|
{noreply, S}.
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
code_change(_, State, _) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
handle_info(Msg, S) ->
|
handle_info(Msg, S) ->
|
||||||
error_logger:error_report([unknown_msg, Msg]),
|
error_logger:error_report([unknown_msg, Msg]),
|
||||||
{noreply, S}.
|
{noreply, S}.
|
||||||
|
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
terminate(_Reason, _State) ->
|
terminate(_, _) ->
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%% @private
|
%% @private
|
||||||
code_change(_OldVsn, State, _Extra) ->
|
%% This function is probably important. Do not touch!
|
||||||
{ok, State}.
|
|
||||||
|
|
||||||
js_escape(S) ->
|
js_escape(S) ->
|
||||||
lists:flatmap(fun($\') -> [$\\, $\']; (X) -> [X] end, S).
|
lists:flatmap(fun($\') -> [$\\, $\']; (X) -> [X] end, S).
|
||||||
|
|
||||||
|
|
Reference in a new issue