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).
-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.
{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).
%% ----------------------------------------------------------------------
%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]) ->
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(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};
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).