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:
Kallfaktorn 2011-04-29 03:30:20 +02:00
parent 0b649c35ac
commit 629c1f6821

View file

@ -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"),
%% @private % JSApp = fetch("jsapp"),
init([Table]) -> % 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), 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 {noreply, State};
%case Ret of %c Old
% ok -> handle_cast({player_command, Key, Player, Command, Args},
% ggs_table:notify_all_players(Table, {"defined", "ok"}), #state { global = Global } = State) ->
% {noreply, State}; Js = "playerCommand(new Player('" ++ Player ++ "'), '" ++
% Other -> js_escape(Command) ++ "', '" ++ js_escape(Args) ++ "');",
% ggs_table:notify_all_players(Table, {"defined", "error " ++ Other}), Global:set_value(Key, Js),
% {noreply, State} erlang:display(binary_to_list(Js)),
%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(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).