Permalink
Browse files

Streaming world

  • Loading branch information...
1 parent 6c8ec55 commit 015cbefa6378e8dfe88e0c1ad826b8263dcdb4fc @ScottBrooks committed Aug 29, 2010
Showing with 271 additions and 38 deletions.
  1. +9 −8 deps/mochiweb-src/ebin/mochiweb.app
  2. +106 −0 src/bloom.erl
  3. +7 −0 src/erlcraft_app.erl
  4. +62 −11 src/erlcraft_client.erl
  5. +11 −7 src/mc.erl
  6. +4 −12 src/mc_reply.erl
  7. +72 −0 src/mc_world.erl
View
17 deps/mochiweb-src/ebin/mochiweb.app
@@ -3,14 +3,15 @@
[{vsn,"1.3"},
{description,"MochiMedia Web Server"},
{modules,[mochifmt,mochifmt_records,mochifmt_std,mochiglobal,
- mochihex,mochijson,mochijson2,mochilists,mochinum,
- mochitemp,mochiutf8,mochiweb,mochiweb_app,
- mochiweb_charref,mochiweb_cookies,mochiweb_cover,
- mochiweb_echo,mochiweb_headers,mochiweb_html,
- mochiweb_http,mochiweb_io,mochiweb_mime,
- mochiweb_multipart,mochiweb_request,mochiweb_response,
- mochiweb_skel,mochiweb_socket,mochiweb_socket_server,
- mochiweb_sup,mochiweb_util,reloader]},
+ mochihex,mochijson,mochijson2,mochilists,
+ mochilogfile2,mochinum,mochitemp,mochiutf8,mochiweb,
+ mochiweb_acceptor,mochiweb_app,mochiweb_charref,
+ mochiweb_cookies,mochiweb_cover,mochiweb_echo,
+ mochiweb_headers,mochiweb_html,mochiweb_http,
+ mochiweb_io,mochiweb_mime,mochiweb_multipart,
+ mochiweb_request,mochiweb_response,mochiweb_skel,
+ mochiweb_socket,mochiweb_socket_server,mochiweb_sup,
+ mochiweb_util,reloader]},
{registered,[]},
{mod,{mochiweb_app,[]}},
{env,[]},
View
106 src/bloom.erl
@@ -0,0 +1,106 @@
+%{author, {"gray", "graygee@gmail.com", {2007, 10, 3}}}.
+%{category, ["type"]}.
+%{name, "bloom"}.
+%{vsn, "0.01"}.
+%{depends, []}.
+%{keywords, ["bloomfilter", "bloom", "filter", "digest", "hash"]}.
+%{summary, "Bloom filters"}.
+%{abstract, "Implements the Bloom filter probabilistic data structure. "
+%"Bloom filters are a space-efficient means to test whether an elements is a "
+%"member of a set."}.
+%{home, "http://code.google.com/p/bloomerl/"}.
+%{source, {erl, "http://bloomerl.googlecode.com/svn/trunk/bloom.erl"}}.
+
+% B0 = bloom:new(2000, 0.001).
+% bloom:is_bloom(B0).
+% B1 = bloom:add_element(Key, B0).
+% bloom:is_element(Key, B1).
+
+
+
+%% @doc Implementation of the Bloom filter data structure.
+%% @reference [http://en.wikipedia.org/wiki/Bloom_filter]
+
+-module(bloom).
+-export([new/1, new/2, is_bloom/1, is_element/2, add_element/2]).
+-import(math, [log/1, pow/2]).
+-import(erlang, [phash2/2]).
+
+-record(bloom, {
+ m = 0, % The size of the bitmap in bits.
+ bitmap = <<>>, % The bitmap.
+ k = 0, % The number of hashes.
+ n = 0, % The maximum number of keys.
+ keys = 0 % The current number of keys.
+}).
+
+%% @spec new(capacity) -> bloom()
+%% @equiv new(capacity, 0.001)
+new(N) -> new(N, 0.001).
+
+%% @spec new(integer(), float()) -> bloom()
+%% @doc Creates a new Bloom filter, given a maximum number of keys and a
+%% false-positive error rate.
+new(N, E) when N > 0, is_float(E), E > 0, E =< 1 ->
+ {M, K} = calc_least_bits(N, E),
+ #bloom{m=M, bitmap = <<0:((M+7) div 8 * 8)>>, k=K, n=N}.
+
+%% @spec is_bloom(bloom()) -> bool()
+%% @doc Determines if the given argument is a bloom record.
+is_bloom(#bloom{}) -> true;
+is_bloom(_) -> false.
+
+%% @spec is_element(string(), bloom()) -> bool()
+%% @doc Determines if the key is (probably) an element of the filter.
+is_element(Key, B) -> is_element(Key, B, calc_idxs(Key, B)).
+is_element(_, _, []) -> true;
+is_element(Key, B, [Idx | T]) ->
+ ByteIdx = Idx div 8,
+ <<_:ByteIdx/binary, Byte:8, _/binary>> = B#bloom.bitmap,
+ Mask = 1 bsl (Idx rem 8),
+ case 0 =/= Byte band Mask of
+ true -> is_element(Key, B, T);
+ false -> false
+ end.
+
+%% @spec add_element(string(), bloom()) -> bloom()
+%% @doc Adds the key to the filter.
+add_element(Key, #bloom{keys=Keys, n=N, bitmap=Bitmap} = B) when Keys < N ->
+ Idxs = calc_idxs(Key, B),
+ Bitmap0 = set_bits(Bitmap, Idxs),
+ case Bitmap0 == Bitmap of
+ true -> B; % Don't increment key count for duplicates.
+ false -> B#bloom{bitmap=Bitmap0, keys=Keys+1}
+ end.
+
+set_bits(Bin, []) -> Bin;
+set_bits(Bin, [Idx | Idxs]) ->
+ ByteIdx = Idx div 8,
+ <<Pre:ByteIdx/binary, Byte:8, Post/binary>> = Bin,
+ Mask = 1 bsl (Idx rem 8),
+ Byte0 = Byte bor Mask,
+ set_bits(<<Pre/binary, Byte0:8, Post/binary>>, Idxs).
+
+% Find the optimal bitmap size and number of hashes.
+calc_least_bits(N, E) -> calc_least_bits(N, E, 1, 0, 0).
+calc_least_bits(N, E, K, MinM, BestK) ->
+ M = -1 * K * N / log(1 - pow(E, 1/K)),
+ {CurM, CurK} = if M < MinM -> {M, K}; true -> {MinM, BestK} end,
+ case K of
+ 1 -> calc_least_bits(N, E, K+1, M, K);
+ 100 -> {trunc(CurM)+1, CurK};
+ _ -> calc_least_bits(N, E, K+1, CurM, CurK)
+ end.
+
+% This uses the "enhanced double hashing" algorithm.
+% Todo: handle case of m > 2^32.
+calc_idxs(Key, #bloom{m=M, k=K}) ->
+ X = phash2(Key, M),
+ Y = phash2({"salt", Key}, M),
+ calc_idxs(M, K - 1, X, Y, [X]).
+calc_idxs(_, 0, _, _, Acc) -> Acc;
+calc_idxs(M, I, X, Y, Acc) ->
+ Xi = (X+Y) rem M,
+ Yi = (Y+I) rem M,
+ calc_idxs(M, I-1, Xi, Yi, [Xi | Acc]).
+
View
7 src/erlcraft_app.erl
@@ -61,6 +61,13 @@ init([Port, Module]) ->
infinity, % Shutdown = brutal_kill | int() >= 0 | infinity
supervisor, % Type = worker | supervisor
[] % Modules = [Module] | dynamic
+ },
+ { mc_world,
+ {mc_world, start_link, [fake]},
+ permanent,
+ 2000,
+ worker,
+ [mc_world]
}
]
}
View
73 src/erlcraft_client.erl
@@ -7,35 +7,86 @@
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
--record(state, {fsm, timer_ref, client_start, loc, player_id}).
+-record(state, {fsm, timer_ref, client_start, loc, chunk_list, player_id}).
+
+
+update_chunks(X, Y, Z, FSMPid, ChunkList) ->
+ NewX = trunc(X/16),
+ NewY = trunc(Y/128),
+ NewZ = trunc(Z/16),
+ Keys = [
+ {pos, NewX, NewY, NewZ},
+ {pos, NewX - 1, NewY, NewZ},
+ {pos, NewX + 1, NewY, NewZ},
+ {pos, NewX, NewY, NewZ - 1},
+ {pos, NewX, NewY, NewZ + 1},
+ {pos, NewX - 1, NewY, NewZ - 1},
+ {pos, NewX + 1, NewY, NewZ + 1},
+ {pos, NewX + 1, NewY, NewZ - 1},
+ {pos, NewX - 1, NewY, NewZ + 1},
+ % nearest 2 neighbors
+ {pos, NewX + 2, NewY, NewZ},
+ {pos, NewX + 2, NewY, NewZ + 1},
+ {pos, NewX + 2, NewY, NewZ - 1},
+ {pos, NewX + 2, NewY, NewZ + 2},
+ {pos, NewX + 2, NewY, NewZ - 2},
+ {pos, NewX - 2, NewY, NewZ},
+ {pos, NewX - 2, NewY, NewZ + 1},
+ {pos, NewX - 2, NewY, NewZ - 1},
+ {pos, NewX - 2, NewY, NewZ + 2},
+ {pos, NewX - 2, NewY, NewZ - 2},
+
+ {pos, NewX + 1, NewY, NewZ + 2},
+ {pos, NewX + 1, NewY, NewZ - 2},
+ {pos, NewX - 1, NewY, NewZ + 2},
+ {pos, NewX - 1, NewY, NewZ - 2},
+ {pos, NewX, NewY, NewZ + 2},
+ {pos, NewX, NewY, NewZ - 2}
+ ],
+ ChunksToLoad = lists:filter(fun(Key) -> sets:is_element(Key, ChunkList) =:= false end, Keys),
+ lists:foldl(fun(Key, Acc) ->
+ {pos, ChunkX, ChunkY, ChunkZ} = Key,
+ {chunk, PreChunk, Chunk} = mc_world:get_chunk(ChunkX, ChunkY, ChunkZ),
+ erlcraft_client_fsm:send_packet(FSMPid, PreChunk),
+ erlcraft_client_fsm:send_packet(FSMPid, Chunk),
+ sets:add_element(Key, Acc)
+ end, ChunkList, ChunksToLoad).
+
+
init([FSMPid]) ->
io:format("Started!~n", []),
{ok, TRef} = timer:send_interval(1000, update_time),
- {ok, #state{fsm = FSMPid, timer_ref = TRef, client_start = now(), loc = undefined, player_id = undefined}}.
+ ChunkList = sets:new(),
+ {ok, #state{fsm = FSMPid, timer_ref = TRef, client_start = now(), loc = undefined, chunk_list = ChunkList, player_id = undefined}}.
handle_call(_Request, _From, _State) ->
io:format("Call: ~p~n", [_Request]),
{reply, none, _State}.
-handle_cast({move_look, X,Y,Z, S, R, P}, #state{fsm = FSMPid, loc = Location} = State) ->
- case Location of
+handle_cast({move_look, X,Y,Z, S, R, P}, #state{fsm = FSMPid, loc = Location, chunk_list = ChunkList} = State) ->
+ {loc, NewX, NewY, NewZ, NewStance, NewRotation, NewPitch} = case Location of
undefined ->
- mc_reply:fake_world(FSMPid, 32, 0, 0, 0),
- LocY = 2.1,
- erlcraft_client_fsm:send_packet(FSMPid, mc_reply:position_and_look(0, LocY * 32, 0, (LocY*32)-1.5, R, P));
- _ -> ok
+ {loc, SpawnX, SpawnY, SpawnZ, SpawnStance, SpawnRotation, SpawnPitch} = mc_world:get_spawn(),
+ erlcraft_client_fsm:send_packet(FSMPid, mc_reply:position_and_look(SpawnX, SpawnY, SpawnZ, SpawnStance, SpawnRotation, SpawnPitch)),
+ {loc, SpawnX, SpawnY, SpawnZ, SpawnStance, SpawnRotation, SpawnPitch};
+ _ -> {loc, X, Y, Z, S, R, P}
end,
+ NewList = update_chunks(NewX, NewY, NewZ, FSMPid, ChunkList),
+ {noreply, State#state{loc = {NewX, NewY, NewZ, NewStance, NewRotation, NewPitch}, chunk_list = NewList}};
- {noreply, State#state{loc = {X, Y, Z, S, R, P}}};
-handle_cast({postion, X, Y, Z, S, _U}, #state{loc = {_, _, _, _, R, P}} = State) ->
- {noreply, State#state{loc = {X, Y, Z, S, R, P}}};
+handle_cast({position, X, Y, Z, S, _U}, #state{loc = {_, _, _, _, R, P}, fsm = FSMPid, chunk_list = ChunkList} = State) ->
+ NewList = update_chunks(X, Y, Z, FSMPid, ChunkList),
+ {noreply, State#state{loc = {X, Y, Z, S, R, P}, chunk_list = NewList}};
handle_cast({look, R, P, _U}, #state{loc = {X, Y, Z, S, _, _}} = State) ->
{noreply, State#state{loc = {X, Y, Z, S, R, P}}};
handle_cast({player_id, PlayerID}, State) ->
{noreply, State#state{player_id = PlayerID}};
+handle_cast({flying, _Flying}, State) ->
+ {noreply, State};
+
handle_cast(_Request, _State) ->
io:format("Cast: ~p~n", [_Request]),
{noreply, _State}.
View
18 src/mc.erl
@@ -24,7 +24,7 @@
-define(PKT_HANDSHAKE(PlayerName), <<16#02, ?PKT_STRING(PlayerName), Rest/binary>>).
-define(PKT_CHAT(Message), <<16#03, ?PKT_STRING(Message), Rest/binary>>).
-define(PKT_UPDATE_TIME(Time), <<16#04, ?PKT_LONG(Time), Rest/binary>>).
--define(PKT_FLYING(Loaded), <<16#0A, ?PKT_BOOL(Flying), Rest/binary>>).
+-define(PKT_FLYING(Flying), <<16#0A, ?PKT_BOOL(Flying), Rest/binary>>).
-define(PKT_PLAYER_POSITION(X,Y,Z,S,U), <<16#0B, ?PKT_DOUBLE(X), ?PKT_DOUBLE(Y), ?PKT_DOUBLE(S), ?PKT_DOUBLE(Z), ?PKT_BOOL(U), Rest/binary>>).
-define(PKT_PLAYER_LOOK(R,P,U), <<16#0C, ?PKT_FLOAT(R), ?PKT_FLOAT(P), ?PKT_BOOL(U), Rest/binary>>).
-define(PKT_PLAYER_MOVE_LOOK(X,Y,Z,S,R,P,U), <<16#0D, ?PKT_DOUBLE(X), ?PKT_DOUBLE(Y), ?PKT_DOUBLE(S), ?PKT_DOUBLE(Z), ?PKT_FLOAT(R), ?PKT_FLOAT(P), ?PKT_BOOL(U), Rest/binary>>).
@@ -144,33 +144,37 @@ handle_data(Data) ->
?PKT_KICK(Message) ->
{done, {kick, Message}, Rest};
_ ->
- io:format("More: [~p]~n", [Data]),
+ %io:format("More: [~p]~n", [Data]),
{more, Data}
end.
handle_packet(_Client, {handshake, PlayerName}) ->
- io:format("C->S: Welcome: ~p~n", [PlayerName]),
+% io:format("C->S: Welcome: ~p~n", [PlayerName]),
mc_reply:handshake(false);
handle_packet(Client, {login, PlayerID, Username, Password}) ->
- io:format("C->S: PlayerID: ~p~nLogin: ~p~nPass: ~p~n", [PlayerID, Username, Password]),
+% io:format("C->S: PlayerID: ~p~nLogin: ~p~nPass: ~p~n", [PlayerID, Username, Password]),
gen_server:cast(Client, {player_id, PlayerID}),
mc_reply:login(PlayerID, "", "");
handle_packet(Client, {player_move_look, X, Y, Z, S, R, P, U}) ->
gen_server:cast(Client, {move_look, X, Y, Z, S, R, P}),
- io:format("C->S: PML: [~p,~p,~p] [~p,~p,~p] ~p~n", [X,Y,Z, S,R,P,U]),
+% io:format("C->S: PML: [~p,~p,~p] [~p,~p,~p] ~p~n", [X,Y,Z, S,R,P,U]),
none;
handle_packet(Client, {player_look, R, P, U}) ->
gen_server:cast(Client, {look, R, P, U}),
- io:format("C->S: L: [~p,~p,~p]~n", [R,P,U]),
+% io:format("C->S: L: [~p,~p,~p]~n", [R,P,U]),
none;
handle_packet(Client, {player_position, X, Y, Z, S, U}) ->
gen_server:cast(Client, {position, X, Y, Z, S, U}),
- io:format("C->S: P: [~p,~p,~p,~p]~p~n", [X, Y, Z, S, U]),
+% io:format("C->S: P: [~p,~p,~p,~p]~p~n", [X, Y, Z, S, U]),
+ none;
+
+handle_packet(Client, {flying, Flying}) ->
+ gen_server:cast(Client, {flying, Flying}),
none;
handle_packet(_State, {loaded, _Loaded}) ->
View
16 src/mc_reply.erl
@@ -20,15 +20,6 @@ position_and_look(X, Y, Z, Stance, Rotation, Pitch) ->
timestamp(Time) ->
mc_util:write_packet(16#4, [{long, Time}]).
-generate_pre_chunk(X,Z, Update) ->
- mc_util:write_packet(16#32, [{int, X}, {int, Z}, {bool, Update}]).
-
-generate_chunk(X, Y, Z, SizeX, SizeY, SizeZ) ->
- Data = mc_util:chunk_data(SizeX * SizeY * SizeX),
- Compressed = zlib:compress(mc_util:encode_list(Data)),
-
- mc_util:write_packet(16#33, lists:flatten([{int, X*16}, {short, Y}, {int, Z*16}, {byte, SizeX-1}, {byte, SizeY-1}, {byte, SizeZ-1}, {int, size(Compressed)}, {binary, Compressed}])).
-
teleport(PlayerID, X, Y, Z, R, P) ->
mc_util:write_packet(16#22, [{int, PlayerID}, {int, X}, {int, Y}, {int, Z}, {byte, R}, {byte, P}]).
entity_spawn(EntityID, X, Y, Z, R, P) ->
@@ -51,12 +42,13 @@ fake_world(Pid, BlockCount, LocX, LocY, LocZ) ->
ChunkX = trunc(LocX + X - (Width/2)),
ChunkZ = trunc(LocZ + Z - (Width/2)),
io:format("Chunk at: [~p, ~p]~n", [ChunkX, ChunkZ]),
- PreChunk = generate_pre_chunk(ChunkX, ChunkZ, 1),
+ %PreChunk = generate_pre_chunk(ChunkX, ChunkZ, 1),
+ %Chunk = generate_chunk(ChunkX,0,ChunkZ, 16,128,16),
+ {chunk, PreChunk, Chunk} = mc_world:get_chunk(ChunkX, 0, ChunkZ),
erlcraft_client_fsm:send_packet(Pid, PreChunk),
- Chunk = generate_chunk(ChunkX,0,ChunkZ, 16,128,16),
erlcraft_client_fsm:send_packet(Pid, Chunk)
end,
lists:seq(0,BlockCount)),
%erlcraft_client_fsm:send_packet(Pid, mc_reply:entity_spawn(1, trunc(LocX)*32, trunc(LocY)*32, trunc(LocZ)*32, 0, 0)),
- ok.
+ {spawn_location, 0, 66, 0}.
View
72 src/mc_world.erl
@@ -0,0 +1,72 @@
+-module(mc_world).
+
+-behaviour(gen_server).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
+
+-record(state, {}).
+
+%% External API
+-export([start_link/1, get_chunk/3, get_spawn/0]).
+
+generate_pre_chunk(X,Z, Update) ->
+ mc_util:write_packet(16#32, [{int, X}, {int, Z}, {bool, Update}]).
+
+generate_chunk(X, Y, Z, SizeX, SizeY, SizeZ) ->
+ Data = mc_util:chunk_data(SizeX * SizeY * SizeX),
+ Compressed = zlib:compress(mc_util:encode_list(Data)),
+ mc_util:write_packet(16#33, lists:flatten([{int, X*16}, {short, Y}, {int, Z*16}, {byte, SizeX-1}, {byte, SizeY-1}, {byte, SizeZ-1}, {int, size(Compressed)}, {binary, Compressed}])).
+
+
+
+get_spawn() ->
+ gen_server:call(?MODULE, {get_spawn}).
+
+get_chunk(X,Y,Z) ->
+ gen_server:call(?MODULE, {get_chunk, trunc(X), trunc(Y), trunc(Z)}).
+
+start_link(World) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [World], []).
+
+
+%% gen_server events
+
+init([_MapName]) ->
+ io:format("World Server started~n", []),
+ {ok, #state{}}.
+
+handle_call({get_chunk, X, Y, Z}, _From, _State) when is_integer(X), is_integer(Z) ->
+ %io:format("Chunk Request: [~p, ~p, ~p]~n", [X, Y, Z]),
+ PreChunk = generate_pre_chunk(X, Z, 1),
+ ChunkData = generate_chunk(X, Y, Z, 16, 128, 16),
+ {reply, {chunk, PreChunk, ChunkData}, _State};
+
+handle_call({get_spawn}, _From, _State) ->
+ {reply, {loc, 0, 96, 0, 94.5, 0.0, 0.0}, _State};
+
+handle_call(_Request, _From, _State) ->
+ io:format("Call: ~p~n", [_Request]),
+ {reply, none, _State}.
+
+handle_cast(_Request, _State) ->
+ io:format("Cast: ~p~n", [_Request]),
+ {noreply, _State}.
+
+handle_info(_Info, _State) ->
+ io:format("Info: ~p~n", [_Info]),
+ {noreply, _State}.
+
+terminate(_Reason, _State) ->
+ io:format("Term: ~p~n", [_Reason]),
+ normal.
+
+code_change(_OldVsn, _State, _Extra) ->
+ {ok, _State}.
+
+%%
+%% Tests
+%%
+-include_lib("eunit/include/eunit.hrl").
+-ifdef(TEST).
+-endif.
+

0 comments on commit 015cbef

Please sign in to comment.