sitä sun tätä tekeillä, toimii kivasti

This commit is contained in:
Perttu Ahola 2010-11-29 10:52:07 +02:00
parent e8fd5eb8ee
commit c707e00195
19 changed files with 998 additions and 761 deletions

@ -2,7 +2,7 @@
# It's usually sufficient to change just the target name and source file list # It's usually sufficient to change just the target name and source file list
# and be sure that CXX is set to a valid compiler # and be sure that CXX is set to a valid compiler
TARGET = test TARGET = test
SOURCE_FILES = mapblockobject.cpp inventory.cpp debug.cpp serialization.cpp light.cpp filesys.cpp connection.cpp environment.cpp client.cpp server.cpp socket.cpp mapblock.cpp mapsector.cpp heightmap.cpp map.cpp player.cpp utility.cpp main.cpp test.cpp SOURCE_FILES = voxel.cpp mapblockobject.cpp inventory.cpp debug.cpp serialization.cpp light.cpp filesys.cpp connection.cpp environment.cpp client.cpp server.cpp socket.cpp mapblock.cpp mapsector.cpp heightmap.cpp map.cpp player.cpp utility.cpp main.cpp test.cpp
SOURCES = $(addprefix src/, $(SOURCE_FILES)) SOURCES = $(addprefix src/, $(SOURCE_FILES))
OBJECTS = $(SOURCES:.cpp=.o) OBJECTS = $(SOURCES:.cpp=.o)
FASTTARGET = fasttest FASTTARGET = fasttest
@ -13,7 +13,7 @@ JTHREADPATH = ../jthread/jthread-1.2.1
CPPFLAGS = -I$(IRRLICHTPATH)/include -I/usr/X11R6/include -I$(JTHREADPATH)/src CPPFLAGS = -I$(IRRLICHTPATH)/include -I/usr/X11R6/include -I$(JTHREADPATH)/src
#CXXFLAGS = -O2 -ffast-math -Wall -fomit-frame-pointer -pipe #CXXFLAGS = -O2 -ffast-math -Wall -fomit-frame-pointer -pipe
CXXFLAGS = -O2 -ffast-math -Wall -g CXXFLAGS = -O2 -ffast-math -Wall -g -pipe
#CXXFLAGS = -O1 -ffast-math -Wall -g #CXXFLAGS = -O1 -ffast-math -Wall -g
#CXXFLAGS = -Wall -g -O0 #CXXFLAGS = -Wall -g -O0
@ -21,8 +21,8 @@ CXXFLAGS = -O2 -ffast-math -Wall -g
#CXXFLAGS = -O3 -ffast-math -Wall -g #CXXFLAGS = -O3 -ffast-math -Wall -g
#CXXFLAGS = -O2 -ffast-math -Wall -g #CXXFLAGS = -O2 -ffast-math -Wall -g
#FASTCXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=pentium3 #FASTCXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=i686
FASTCXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=i686 FASTCXXFLAGS = -O3 -ffast-math -Wall -fomit-frame-pointer -pipe -funroll-loops -mtune=i686 -fwhole-program
#Default target #Default target
@ -53,7 +53,9 @@ all_linux all_win32: $(DESTPATH)
fast_linux: $(FASTDESTPATH) fast_linux: $(FASTDESTPATH)
$(FASTDESTPATH): $(SOURCES) $(FASTDESTPATH): $(SOURCES)
$(CXX) -o $(FASTDESTPATH) $(SOURCES) $(CPPFLAGS) $(FASTCXXFLAGS) $(LDFLAGS) -DUNITTEST_DISABLE @#$(CXX) -o $(FASTDESTPATH) $(SOURCES) $(CPPFLAGS) $(FASTCXXFLAGS) $(LDFLAGS) -DUNITTEST_DISABLE
@# Errno doesn't work ("error: __errno_location was not declared in this scope")
cat $(SOURCES) | $(CXX) -o $(FASTDESTPATH) -x c++ - -Isrc/ $(CPPFLAGS) $(FASTCXXFLAGS) $(LDFLAGS) -DUNITTEST_DISABLE -DDISABLE_ERRNO
$(DESTPATH): $(OBJECTS) $(DESTPATH): $(OBJECTS)
$(CXX) -o $@ $(OBJECTS) $(LDFLAGS) $(CXX) -o $@ $(OBJECTS) $(LDFLAGS)

BIN
data/mud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 831 B

@ -32,6 +32,7 @@ cp -r data/light.png $PACKAGEPATH/data/
cp -r data/sign.png $PACKAGEPATH/data/ cp -r data/sign.png $PACKAGEPATH/data/
cp -r data/sign_back.png $PACKAGEPATH/data/ cp -r data/sign_back.png $PACKAGEPATH/data/
cp -r data/rat.png $PACKAGEPATH/data/ cp -r data/rat.png $PACKAGEPATH/data/
cp -r data/mud.png $PACKAGEPATH/data/
cp -r doc/README.txt $PACKAGEPATH/doc/README.txt cp -r doc/README.txt $PACKAGEPATH/doc/README.txt

@ -44,4 +44,8 @@ creative_mode = false
# Player and object positions are sent at intervals specified by this # Player and object positions are sent at intervals specified by this
objectdata_inverval = 0.2 objectdata_inverval = 0.2
active_object_range = 2
max_simultaneous_block_sends_per_client = 2
max_simultaneous_block_sends_server_total = 4

@ -31,7 +31,7 @@ void * ClientUpdateThread::Thread()
bool was = m_client->AsyncProcessData(); bool was = m_client->AsyncProcessData();
if(was == false) if(was == false)
sleep_ms(50); sleep_ms(10);
} }
#if CATCH_UNHANDLED_EXCEPTIONS #if CATCH_UNHANDLED_EXCEPTIONS
} }
@ -159,13 +159,17 @@ void Client::step(float dtime)
{ {
/* /*
Delete unused sectors Delete unused sectors
NOTE: This jams the game for a while because deleting sectors
clear caches
*/ */
static float counter = -0.001; static float counter = -0.001;
counter -= dtime; counter -= dtime;
if(counter <= 0.0) if(counter <= 0.0)
{ {
counter = 10.0; // 3 minute interval
counter = 180.0;
JMutexAutoLock lock(m_env_mutex); JMutexAutoLock lock(m_env_mutex);
@ -381,6 +385,8 @@ float Client::asyncStep()
/*float dtime; /*float dtime;
{ {
JMutexAutoLock lock1(m_step_dtime_mutex); JMutexAutoLock lock1(m_step_dtime_mutex);
if(m_step_dtime < 0.001)
return 0.0;
dtime = m_step_dtime; dtime = m_step_dtime;
m_step_dtime = 0.0; m_step_dtime = 0.0;
} }
@ -1207,6 +1213,18 @@ bool Client::AsyncProcessPacket(LazyMeshUpdater &mesh_updater)
bool Client::AsyncProcessData() bool Client::AsyncProcessData()
{ {
for(;;)
{
// We want to update the meshes as soon as a single packet has
// been processed
LazyMeshUpdater mesh_updater(&m_env);
bool r = AsyncProcessPacket(mesh_updater);
if(r == false)
break;
}
return false;
/*
LazyMeshUpdater mesh_updater(&m_env); LazyMeshUpdater mesh_updater(&m_env);
for(;;) for(;;)
{ {
@ -1214,7 +1232,7 @@ bool Client::AsyncProcessData()
if(r == false) if(r == false)
break; break;
} }
return false; return false;*/
} }
void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable) void Client::Send(u16 channelnum, SharedBuffer<u8> data, bool reliable)

@ -28,19 +28,20 @@
// The absolute working limit is (2^15 - viewing_range). // The absolute working limit is (2^15 - viewing_range).
#define MAP_GENERATION_LIMIT (31000) #define MAP_GENERATION_LIMIT (31000)
#define MAX_SIMULTANEOUS_BLOCK_SENDS 2 //#define MAX_SIMULTANEOUS_BLOCK_SENDS 2
#define FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING 2.0 #define FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING 2.0
#define LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS 1 //#define LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS 1
#define LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS 0
#define MAX_SIMULTANEOUS_BLOCK_SENDS_SERVER_TOTAL 4 // Override for the previous one when distance is low
#define BLOCK_SEND_DISABLE_LIMITS_MAX_D 1
//#define MAX_SIMULTANEOUS_BLOCK_SENDS_SERVER_TOTAL 4
// Viewing range stuff // Viewing range stuff
#define FPS_DEFAULT_WANTED 30 //#define HEIGHTMAP_RANGE_NODES 300
#define FPS_DEFAULT_MAX 60
#define HEIGHTMAP_RANGE_NODES 300
//#define FREETIME_RATIO 0.2 //#define FREETIME_RATIO 0.2
#define FREETIME_RATIO 0.15 #define FREETIME_RATIO 0.15
@ -56,7 +57,7 @@
//#define ACTIVE_OBJECT_D_BLOCKS 2 //#define ACTIVE_OBJECT_D_BLOCKS 2
// Wether to catch all std::exceptions // Wether to catch all std::exceptions
#define CATCH_UNJANDLED_EXCEPTIONS 1 #define CATCH_UNHANDLED_EXCEPTIONS 0
/* /*
Collecting active blocks is stopped after object data Collecting active blocks is stopped after object data

@ -81,6 +81,22 @@ public:
{} {}
}; };
class SettingNotFoundException : public BaseException
{
public:
SettingNotFoundException(const char *s):
BaseException(s)
{}
};
class InvalidFilenameException : public BaseException
{
public:
InvalidFilenameException(const char *s):
BaseException(s)
{}
};
/* /*
Some "old-style" interrupts: Some "old-style" interrupts:
*/ */

@ -21,11 +21,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
=============================== NOTES ============================== =============================== NOTES ==============================
NOTE: VBO cannot be turned on for fast-changing stuff because there NOTE: VBO cannot be turned on for fast-changing stuff because there
is an apparanet memory leak in irrlicht when using it is an apparanet memory leak in irrlicht when using it (not sure)
NOTE: iostream.imbue(std::locale("C")) is very slow
NOTE: Global locale is now set at initialization
SUGGESTION: add a second lighting value to the MS nibble of param of SUGGESTION: add a second lighting value to the MS nibble of param of
air to tell how bright the air node is when there is no sunlight. air to tell how bright the air node is when there is no sunlight.
When day changes to night, these two values can be interpolated. When day changes to night, these two values can be interpolated.
TODO: Fix address to be ipv6 compatible TODO: Fix address to be ipv6 compatible
TODO: ESC Pause mode in which the cursor is not kept at the center of window. TODO: ESC Pause mode in which the cursor is not kept at the center of window.
@ -93,9 +97,7 @@ TODO: Expose Connection's seqnums and ACKs to server and client.
- This also enables server to check if client has received the - This also enables server to check if client has received the
most recent block sent, for example. most recent block sent, for example.
SUGG: Add a time value to the param of footstepped grass and check it TODO: Add a sane bandwidth throttling system to Connection
against a global timer when a block is accessed, to make old
steps fade away.
FIXME: There still are *some* tiny glitches in lighting as seen from FIXME: There still are *some* tiny glitches in lighting as seen from
the client side. The server calculates them right but sometimes the client side. The server calculates them right but sometimes
@ -105,8 +107,9 @@ FIXME: There still are *some* tiny glitches in lighting as seen from
the sender sends the block as it was before emerging? the sender sends the block as it was before emerging?
TODO: How about adding a "revision" field to MapBlocks? TODO: How about adding a "revision" field to MapBlocks?
TODO: More fine-grained control of client's dumping of blocks from SUGG: More fine-grained control of client's dumping of blocks from
memory memory
- ...What does this mean in the first place?
TODO: Somehow prioritize the sending of blocks and combine the block TODO: Somehow prioritize the sending of blocks and combine the block
send queue lengths send queue lengths
@ -130,9 +133,6 @@ SUGG: Make client send GOTBLOCKS before updating meshes
TODO: Server to load starting inventory from disk TODO: Server to load starting inventory from disk
NOTE: iostream.imbue(std::locale("C")) is very slow
NOTE: Global locale is now set at initialization
TODO: PLayers to only be hidden when the client quits. TODO: PLayers to only be hidden when the client quits.
TODO: - Players to be saved on disk, with inventory TODO: - Players to be saved on disk, with inventory
TODO: Players to be saved as text in map/players/<name> TODO: Players to be saved as text in map/players/<name>
@ -158,12 +158,18 @@ Block object server side:
- A "near blocks" buffer, in which some nearby blocks are stored. - A "near blocks" buffer, in which some nearby blocks are stored.
- For all blocks in the buffer, objects are stepped(). This - For all blocks in the buffer, objects are stepped(). This
means they are active. means they are active.
- TODO All blocks going in and out of the buffer are recorded. - TODO: A global active buffer is needed for the server
- TODO For outgoing blocks, a timestamp is written. - TODO: All blocks going in and out of the buffer are recorded.
- TODO For incoming blocks, the time difference is calculated and - TODO: For outgoing blocks, a timestamp is written.
- TODO: For incoming blocks, the time difference is calculated and
objects are stepped according to it. objects are stepped according to it.
TODO: A timestamp to blocks TODO: A timestamp to blocks
SUGG: Add a time value to the param of footstepped grass and check it
against a global timer when a block is accessed, to make old
steps fade away.
TODO: Add config parameters for server's sending and generating distance TODO: Add config parameters for server's sending and generating distance
TODO: Copy the text of the last picked sign to inventory in creative TODO: Copy the text of the last picked sign to inventory in creative
@ -185,12 +191,16 @@ SUGG: Split MapBlockObject serialization to to-client and to-disk
- This will allow saving ages of rats on disk but not sending - This will allow saving ages of rats on disk but not sending
them to clients them to clients
TODO: Fix the long-lived Server Block Emerge Jam bug TODO: Get rid of GotSplitPacketException
- Is it related to the client deleting blocks?
Before release:
TODO: Check what goes wrong with caching map to disk (Kray)
Doing now: Doing now:
====================================================================== ======================================================================
TODO: Implement lighting using VoxelManipulator
====================================================================== ======================================================================
@ -202,7 +212,7 @@ Doing now:
the starting place to a static direction. the starting place to a static direction.
This allows one to move around with the player and see what This allows one to move around with the player and see what
is actually drawn behind solid things etc. is actually drawn behind solid things and behind the player.
*/ */
#define FIELD_OF_VIEW_TEST 0 #define FIELD_OF_VIEW_TEST 0
@ -265,7 +275,8 @@ const char *g_material_filenames[MATERIALS_COUNT] =
"../data/tree.png", "../data/tree.png",
"../data/leaves.png", "../data/leaves.png",
"../data/grass_footsteps.png", "../data/grass_footsteps.png",
"../data/mese.png" "../data/mese.png",
"../data/mud.png"
}; };
video::SMaterial g_materials[MATERIALS_COUNT]; video::SMaterial g_materials[MATERIALS_COUNT];
@ -296,28 +307,39 @@ bool g_viewing_range_all = false;
These are loaded from the config file. These are loaded from the config file.
*/ */
std::string g_dedicated_server; Settings g_settings;
// Client stuff // Sets default settings
float g_wanted_fps = FPS_DEFAULT_WANTED; void set_default_settings()
float g_fps_max = FPS_DEFAULT_MAX; {
s16 g_viewing_range_nodes_max = 300; g_settings.set("dedicated_server", "");
s16 g_viewing_range_nodes_min = 20;
std::string g_screenW;
std::string g_screenH;
std::string g_host_game;
std::string g_port;
std::string g_address;
std::string g_name;
bool g_random_input = false;
float g_client_delete_unused_sectors_timeout = 1200;
// Server stuff // Client stuff
bool g_creative_mode = false; g_settings.set("wanted_fps", "30");
HMParams g_hm_params; g_settings.set("fps_max", "60");
MapParams g_map_params; g_settings.set("viewing_range_nodes_max", "300");
float g_objectdata_interval = 0.2; g_settings.set("viewing_range_nodes_min", "20");
u16 g_active_object_range = 2; g_settings.set("screenW", "");
g_settings.set("screenH", "");
g_settings.set("host_game", "");
g_settings.set("port", "");
g_settings.set("address", "");
g_settings.set("name", "");
g_settings.set("random_input", "false");
g_settings.set("client_delete_unused_sectors_timeout", "1200");
// Server stuff
g_settings.set("creative_mode", "false");
g_settings.set("heightmap_blocksize", "128");
g_settings.set("height_randmax", "constant 70.0");
g_settings.set("height_randfactor", "constant 0.6");
g_settings.set("height_base", "linear 0 35 0");
g_settings.set("plants_amount", "1.0");
g_settings.set("objectdata_interval", "0.2");
g_settings.set("active_object_range", "2");
g_settings.set("max_simultaneous_block_sends_per_client", "2");
g_settings.set("max_simultaneous_block_sends_server_total", "4");
}
/* /*
Random stuff Random stuff
@ -354,144 +376,6 @@ std::ostream *derr_server_ptr = &dstream;
std::ostream *dout_client_ptr = &dstream; std::ostream *dout_client_ptr = &dstream;
std::ostream *derr_client_ptr = &dstream; std::ostream *derr_client_ptr = &dstream;
/*
Config stuff
*/
// Returns false on EOF
bool parseConfigObject(std::istream &is)
{
// float g_wanted_fps
// s16 g_viewing_range_nodes_max
if(is.eof())
return false;
std::string line;
std::getline(is, line);
//dstream<<"got line: \""<<line<<"\""<<std::endl;
std::string trimmedline = trim(line);
// Ignore comments
if(trimmedline[0] == '#')
return true;
//dstream<<"trimmedline=\""<<trimmedline<<"\""<<std::endl;
Strfnd sf(trim(line));
std::string name = sf.next("=");
name = trim(name);
if(name == "")
return true;
std::string value = sf.next("\n");
value = trim(value);
dstream<<"Config name=\""<<name<<"\" value=\""
<<value<<"\""<<std::endl;
if(name == "dedicated_server")
g_dedicated_server = value;
// Client stuff
else if(name == "wanted_fps")
{
g_wanted_fps = atof(value.c_str());
}
else if(name == "fps_max")
{
g_fps_max = atof(value.c_str());
}
else if(name == "viewing_range_nodes_max")
{
g_viewing_range_nodes_max = stoi(value, 0, 32767);
}
else if(name == "viewing_range_nodes_min")
{
g_viewing_range_nodes_min = stoi(value, 0, 32767);
}
else if(name=="screenW")
g_screenW = value;
else if(name=="screenH")
g_screenH = value;
else if(name == "host_game")
g_host_game = value;
else if(name == "port")
g_port = value;
else if(name == "address")
g_address = value;
else if(name == "name")
g_name = value;
else if(name == "random_input")
g_random_input = is_yes(value);
else if(name == "client_delete_unused_sectors_timeout")
{
std::istringstream vis(value);
//vis.imbue(std::locale("C"));
vis>>g_client_delete_unused_sectors_timeout;
}
// Server stuff
else if(name == "creative_mode")
g_creative_mode = is_yes(value);
else if(name == "mapgen_heightmap_blocksize")
{
s32 d = atoi(value.c_str());
if(d > 0 && (d & (d-1)) == 0)
g_hm_params.heightmap_blocksize = d;
else
dstream<<"Invalid value in config file: \""
<<line<<"\""<<std::endl;
}
else if(name == "mapgen_height_randmax")
g_hm_params.height_randmax = value;
else if(name == "mapgen_height_randfactor")
g_hm_params.height_randfactor = value;
else if(name == "mapgen_height_base")
g_hm_params.height_base = value;
else if(name == "mapgen_plants_amount")
{
std::istringstream vis(value);
vis>>g_map_params.plants_amount;
}
else if(name == "objectdata_inverval")
{
std::istringstream vis(value);
vis>>g_objectdata_interval;
}
else if(name == "active_object_range")
g_active_object_range = stoi(value, 0, 65535);
else
{
dstream<<"Unknown option in config file: \""
<<line<<"\""<<std::endl;
}
return true;
}
// Returns true on success
bool readConfigFile(const char *filename)
{
std::ifstream is(filename);
if(is.good() == false)
{
dstream<<DTIME<<"Error opening configuration file: "
<<filename<<std::endl;
return false;
}
dstream<<DTIME<<"Parsing configuration file: "
<<filename<<std::endl;
while(parseConfigObject(is));
return true;
}
/* /*
Timestamp stuff Timestamp stuff
@ -875,9 +759,11 @@ void updateViewingRange(f32 frametime, Client *client)
// Range_all messes up frametime_avg // Range_all messes up frametime_avg
if(g_viewing_range_all == true) if(g_viewing_range_all == true)
return; return;
float wanted_fps = g_settings.getFloat("wanted_fps");
// Initialize to the target value // Initialize to the target value
static float frametime_avg = 1.0/g_wanted_fps; static float frametime_avg = 1.0/wanted_fps;
frametime_avg = frametime_avg * 0.9 + frametime * 0.1; frametime_avg = frametime_avg * 0.9 + frametime * 0.1;
static f32 counter = 0; static f32 counter = 0;
@ -892,7 +778,7 @@ void updateViewingRange(f32 frametime, Client *client)
//float freetime_ratio = 0.4; //float freetime_ratio = 0.4;
float freetime_ratio = FREETIME_RATIO; float freetime_ratio = FREETIME_RATIO;
float frametime_wanted = (1.0/(g_wanted_fps/(1.0-freetime_ratio))); float frametime_wanted = (1.0/(wanted_fps/(1.0-freetime_ratio)));
float fraction = sqrt(frametime_avg / frametime_wanted); float fraction = sqrt(frametime_avg / frametime_wanted);
@ -925,11 +811,14 @@ void updateViewingRange(f32 frametime, Client *client)
JMutexAutoLock lock(g_range_mutex); JMutexAutoLock lock(g_range_mutex);
s16 viewing_range_nodes_min = g_settings.getS16("viewing_range_nodes_min");
s16 viewing_range_nodes_max = g_settings.getS16("viewing_range_nodes_max");
s16 n = (float)g_viewing_range_nodes / fraction; s16 n = (float)g_viewing_range_nodes / fraction;
if(n < g_viewing_range_nodes_min) if(n < viewing_range_nodes_min)
n = g_viewing_range_nodes_min; n = viewing_range_nodes_min;
if(n > g_viewing_range_nodes_max) if(n > viewing_range_nodes_max)
n = g_viewing_range_nodes_max; n = viewing_range_nodes_max;
bool can_change = true; bool can_change = true;
@ -1050,10 +939,11 @@ int main(int argc, char *argv[])
disable_stderr = true; disable_stderr = true;
#endif #endif
// Initialize debug streams
debugstreams_init(disable_stderr, DEBUGFILE); debugstreams_init(disable_stderr, DEBUGFILE);
// Initialize debug stacks
debug_stacks_init(); debug_stacks_init();
DSTACK(__FUNCTION_NAME); DSTACK(__FUNCTION_NAME);
try try
@ -1063,6 +953,10 @@ int main(int argc, char *argv[])
Basic initialization Basic initialization
*/ */
// Initialize default settings
set_default_settings();
// Print startup message
dstream<<DTIME<<"minetest-c55" dstream<<DTIME<<"minetest-c55"
" with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST " with SER_FMT_VER_HIGHEST="<<(int)SER_FMT_VER_HIGHEST
<<", ENABLE_TESTS="<<ENABLE_TESTS <<", ENABLE_TESTS="<<ENABLE_TESTS
@ -1096,7 +990,7 @@ int main(int argc, char *argv[])
if(argc >= 2) if(argc >= 2)
{ {
readConfigFile(argv[1]); g_settings.readConfigFile(argv[1]);
} }
else else
{ {
@ -1108,7 +1002,7 @@ int main(int argc, char *argv[])
for(u32 i=0; i<2; i++) for(u32 i=0; i<2; i++)
{ {
bool r = readConfigFile(filenames[i]); bool r = g_settings.readConfigFile(filenames[i]);
if(r) if(r)
break; break;
} }
@ -1120,6 +1014,17 @@ int main(int argc, char *argv[])
g_range_mutex.Init(); g_range_mutex.Init();
assert(g_range_mutex.IsInitialized()); assert(g_range_mutex.IsInitialized());
// Read map parameters from settings
HMParams hm_params;
hm_params.blocksize = g_settings.getU16("heightmap_blocksize");
hm_params.randmax = g_settings.get("height_randmax");
hm_params.randfactor = g_settings.get("height_randfactor");
hm_params.base = g_settings.get("height_base");
MapParams map_params;
map_params.plants_amount = g_settings.getFloat("plants_amount");
/* /*
Ask some stuff Ask some stuff
*/ */
@ -1127,40 +1032,13 @@ int main(int argc, char *argv[])
std::cout<<std::endl<<std::endl; std::cout<<std::endl<<std::endl;
char templine[100]; char templine[100];
std::cout<<"Dedicated server? [y = yes]: "; // Dedicated?
if(g_dedicated_server != "") bool dedicated = g_settings.getBoolAsk
{ ("dedicated_server", "Dedicated server?", false);
std::cout<<g_dedicated_server<<std::endl; std::cout<<"dedicated = "<<dedicated<<std::endl;
snprintf(templine, 100, "%s", g_dedicated_server.c_str());
}
else
{
std::cin.getline(templine, 100);
}
bool dedicated = false;
if(templine[0] == 'y')
dedicated = true;
if(dedicated)
std::cout<<"-> yes"<<std::endl;
else
std::cout<<"-> no"<<std::endl;
std::cout<<"Port [empty=30000]: "; // Port?
if(g_port != "") u16 port = g_settings.getU16Ask("port", "Port", 30000);
{
std::cout<<g_port<<std::endl;
snprintf(templine, 100, "%s", g_port.c_str());
}
else
{
std::cin.getline(templine, 100);
}
unsigned short port;
if(templine[0] == 0)
port = 30000;
else
port = atoi(templine);
std::cout<<"-> "<<port<<std::endl; std::cout<<"-> "<<port<<std::endl;
if(dedicated) if(dedicated)
@ -1173,17 +1051,15 @@ int main(int argc, char *argv[])
std::cout<<"========================"<<std::endl; std::cout<<"========================"<<std::endl;
std::cout<<std::endl; std::cout<<std::endl;
Server server("../map", g_creative_mode, g_hm_params, Server server("../map", hm_params, map_params);
g_map_params, g_objectdata_interval,
g_active_object_range);
server.start(port); server.start(port);
for(;;) for(;;)
{ {
// This is kind of a hack but can be done like this // This is kind of a hack but can be done like this
// because server.step() is very light // because server.step() is very light
sleep_ms(100); sleep_ms(30);
server.step(0.1); server.step(0.030);
static int counter = 0; static int counter = 0;
counter--; counter--;
@ -1214,10 +1090,10 @@ int main(int argc, char *argv[])
char connect_name[100] = ""; char connect_name[100] = "";
std::cout<<"Address to connect to [empty = host a game]: "; std::cout<<"Address to connect to [empty = host a game]: ";
if(g_address != "" && is_yes(g_host_game) == false) if(g_settings.get("address") != "" && is_yes(g_settings.get("host_game")) == false)
{ {
std::cout<<g_address<<std::endl; std::cout<<g_settings.get("address")<<std::endl;
snprintf(connect_name, 100, "%s", g_address.c_str()); snprintf(connect_name, 100, "%s", g_settings.get("address").c_str());
} }
else else
{ {
@ -1235,9 +1111,9 @@ int main(int argc, char *argv[])
std::cout<<"-> "<<connect_name<<std::endl; std::cout<<"-> "<<connect_name<<std::endl;
char playername[PLAYERNAME_SIZE] = ""; char playername[PLAYERNAME_SIZE] = "";
if(g_name != "") if(g_settings.get("name") != "")
{ {
snprintf(playername, PLAYERNAME_SIZE, "%s", g_name.c_str()); snprintf(playername, PLAYERNAME_SIZE, "%s", g_settings.get("name").c_str());
} }
else else
{ {
@ -1254,10 +1130,10 @@ int main(int argc, char *argv[])
u16 screenH; u16 screenH;
bool fullscreen = false; bool fullscreen = false;
if(g_screenW != "" && g_screenH != "") if(g_settings.get("screenW") != "" && g_settings.get("screenH") != "")
{ {
screenW = atoi(g_screenW.c_str()); screenW = atoi(g_settings.get("screenW").c_str());
screenH = atoi(g_screenH.c_str()); screenH = atoi(g_settings.get("screenH").c_str());
} }
else else
{ {
@ -1343,7 +1219,7 @@ int main(int argc, char *argv[])
device->setResizable(true); device->setResizable(true);
if(g_random_input) if(g_settings.getBool("random_input"))
g_input = new RandomInputHandler(); g_input = new RandomInputHandler();
else else
g_input = new RealInputHandler(device, &receiver); g_input = new RealInputHandler(device, &receiver);
@ -1443,9 +1319,7 @@ int main(int argc, char *argv[])
*/ */
SharedPtr<Server> server; SharedPtr<Server> server;
if(hosting){ if(hosting){
server = new Server("../map", g_creative_mode, g_hm_params, server = new Server("../map", hm_params, map_params);
g_map_params, g_objectdata_interval,
g_active_object_range);
server->start(port); server->start(port);
} }
@ -1455,7 +1329,7 @@ int main(int argc, char *argv[])
// TODO: Get rid of the g_materials parameter or it's globalness // TODO: Get rid of the g_materials parameter or it's globalness
Client client(device, g_materials, Client client(device, g_materials,
g_client_delete_unused_sectors_timeout, g_settings.getFloat("client_delete_unused_sectors_timeout"),
playername); playername);
Address connect_address(0,0,0,0, port); Address connect_address(0,0,0,0, port);
@ -1648,7 +1522,7 @@ int main(int argc, char *argv[])
*/ */
{ {
float fps_max = g_fps_max; float fps_max = g_settings.getFloat("fps_max");
u32 frametime_min = 1000./fps_max; u32 frametime_min = 1000./fps_max;
if(busytime_u32 < frametime_min) if(busytime_u32 < frametime_min)
@ -2326,7 +2200,7 @@ int main(int argc, char *argv[])
delete g_input; delete g_input;
/* /*
In the end, delete the Irrlicht device. In the end, delete the Irrlicht device.
*/ */
device->drop(); device->drop();

@ -16,6 +16,9 @@ extern s16 g_viewing_range_nodes;
//extern s16 g_actual_viewing_range_nodes; //extern s16 g_actual_viewing_range_nodes;
extern bool g_viewing_range_all; extern bool g_viewing_range_all;
// Settings
extern Settings g_settings;
#include <fstream> #include <fstream>
// Debug streams // Debug streams

@ -9,6 +9,7 @@
#include "client.h" #include "client.h"
#include "filesys.h" #include "filesys.h"
#include "utility.h" #include "utility.h"
#include "voxel.h"
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #include <windows.h>
@ -18,6 +19,47 @@
#define sleep_ms(x) usleep(x*1000) #define sleep_ms(x) usleep(x*1000)
#endif #endif
MapBlockPointerCache::MapBlockPointerCache(Map *map)
{
m_map = map;
m_map->m_blockcachelock.cacheCreated();
m_from_cache_count = 0;
m_from_map_count = 0;
}
MapBlockPointerCache::~MapBlockPointerCache()
{
m_map->m_blockcachelock.cacheRemoved();
dstream<<"MapBlockPointerCache:"
<<" from_cache_count="<<m_from_cache_count
<<" from_map_count="<<m_from_map_count
<<std::endl;
}
MapBlock * MapBlockPointerCache::getBlockNoCreate(v3s16 p)
{
core::map<v3s16, MapBlock*>::Node *n = NULL;
n = m_blocks.find(p);
if(n != NULL)
{
m_from_cache_count++;
return n->getValue();
}
m_from_map_count++;
// Throws InvalidPositionException if not found
MapBlock *b = m_map->getBlockNoCreate(p);
m_blocks[p] = b;
return b;
}
/*
Map
*/
Map::Map(std::ostream &dout): Map::Map(std::ostream &dout):
m_dout(dout), m_dout(dout),
m_camera_position(0,0,0), m_camera_position(0,0,0),
@ -158,33 +200,11 @@ bool Map::isNodeUnderground(v3s16 p)
} }
} }
#ifdef LKJnb #if 0
//TODO: Remove: Not used. void Map::interpolate(v3s16 block,
/* core::map<v3s16, MapBlock*> & modified_blocks)
Goes recursively through the neighbours of the node.
Alters only transparent nodes.
If the lighting of the neighbour is lower than the lighting of
the node was (before changing it to 0 at the step before), the
lighting of the neighbour is set to 0 and then the same stuff
repeats for the neighbour.
Some things are made strangely to make it as fast as possible.
Usage: (for clearing all possible spreaded light of a lamp)
NOTE: This is outdated
core::list<v3s16> light_sources;
core::map<v3s16, MapBlock*> modified_blocks;
u8 oldlight = node_at_pos.light;
node_at_pos.setLight(0);
unLightNeighbors(pos, oldlight, light_sources, modified_blocks);
*/
void Map::unLightNeighbors(v3s16 pos, u8 oldlight,
core::map<v3s16, bool> & light_sources,
core::map<v3s16, MapBlock*> & modified_blocks)
{ {
v3s16 dirs[6] = { const v3s16 dirs[6] = {
v3s16(0,0,1), // back v3s16(0,0,1), // back
v3s16(0,1,0), // top v3s16(0,1,0), // top
v3s16(1,0,0), // right v3s16(1,0,0), // right
@ -192,7 +212,16 @@ void Map::unLightNeighbors(v3s16 pos, u8 oldlight,
v3s16(0,-1,0), // bottom v3s16(0,-1,0), // bottom
v3s16(-1,0,0), // left v3s16(-1,0,0), // left
}; };
if(from_nodes.size() == 0)
return;
u32 blockchangecount = 0;
core::map<v3s16, bool> lighted_nodes;
core::map<v3s16, bool>::Iterator j;
j = from_nodes.getIterator();
/* /*
Initialize block cache Initialize block cache
*/ */
@ -201,23 +230,22 @@ void Map::unLightNeighbors(v3s16 pos, u8 oldlight,
// Cache this a bit, too // Cache this a bit, too
bool block_checked_in_modified = false; bool block_checked_in_modified = false;
// Loop through 6 neighbors for(; j.atEnd() == false; j++)
for(u16 i=0; i<6; i++){ //for(; j != from_nodes.end(); j++)
// Get the position of the neighbor node {
v3s16 n2pos = pos + dirs[i]; v3s16 pos = j.getNode()->getKey();
//v3s16 pos = *j;
//dstream<<"pos=("<<pos.X<<","<<pos.Y<<","<<pos.Z<<")"<<std::endl;
v3s16 blockpos = getNodeBlockPos(pos);
// Get the block where the node is located
v3s16 blockpos = getNodeBlockPos(n2pos);
// Only fetch a new block if the block position has changed // Only fetch a new block if the block position has changed
try{ try{
if(block == NULL || blockpos != blockpos_last) if(block == NULL || blockpos != blockpos_last){
{
block = getBlockNoCreate(blockpos); block = getBlockNoCreate(blockpos);
blockpos_last = blockpos; blockpos_last = blockpos;
block_checked_in_modified = false; block_checked_in_modified = false;
//blockchangecount++; blockchangecount++;
} }
} }
catch(InvalidPositionException &e) catch(InvalidPositionException &e)
@ -227,33 +255,75 @@ void Map::unLightNeighbors(v3s16 pos, u8 oldlight,
if(block->isDummy()) if(block->isDummy())
continue; continue;
// Calculate relative position in block // Calculate relative position in block
v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE; v3s16 relpos = pos - blockpos_last * MAP_BLOCKSIZE;
// Get node straight from the block // Get node straight from the block
MapNode n2 = block->getNode(relpos); MapNode n = block->getNode(relpos);
/* u8 oldlight = n.getLight();
If the neighbor is dimmer than what was specified u8 newlight = diminish_light(oldlight);
as oldlight (the light of the previous node)
*/ // Loop through 6 neighbors
if(n2.getLight() < oldlight) for(u16 i=0; i<6; i++){
{ // Get the position of the neighbor node
/* v3s16 n2pos = pos + dirs[i];
And the neighbor is transparent and it has some light
*/ // Get the block where the node is located
if(n2.light_propagates() && n2.getLight() != 0) v3s16 blockpos = getNodeBlockPos(n2pos);
try
{ {
/* // Only fetch a new block if the block position has changed
Set light to 0 and recurse. try{
*/ if(block == NULL || blockpos != blockpos_last){
u8 current_light = n2.getLight(); block = getBlockNoCreate(blockpos);
n2.setLight(0); blockpos_last = blockpos;
block->setNode(relpos, n2);
unLightNeighbors(n2pos, current_light, block_checked_in_modified = false;
light_sources, modified_blocks); blockchangecount++;
}
}
catch(InvalidPositionException &e)
{
continue;
}
if(block_checked_in_modified == false) // Calculate relative position in block
v3s16 relpos = n2pos - blockpos * MAP_BLOCKSIZE;
// Get node straight from the block
MapNode n2 = block->getNode(relpos);
bool changed = false;
/*
If the neighbor is brighter than the current node,
add to list (it will light up this node on its turn)
*/
if(n2.getLight() > undiminish_light(oldlight))
{
lighted_nodes.insert(n2pos, true);
//lighted_nodes.push_back(n2pos);
changed = true;
}
/*
If the neighbor is dimmer than how much light this node
would spread on it, add to list
*/
if(n2.getLight() < newlight)
{
if(n2.light_propagates())
{
n2.setLight(newlight);
block->setNode(relpos, n2);
lighted_nodes.insert(n2pos, true);
//lighted_nodes.push_back(n2pos);
changed = true;
}
}
// Add to modified_blocks
if(changed == true && block_checked_in_modified == false)
{ {
// If the block is not found in modified_blocks, add. // If the block is not found in modified_blocks, add.
if(modified_blocks.find(blockpos) == NULL) if(modified_blocks.find(blockpos) == NULL)
@ -263,12 +333,20 @@ void Map::unLightNeighbors(v3s16 pos, u8 oldlight,
block_checked_in_modified = true; block_checked_in_modified = true;
} }
} }
} catch(InvalidPositionException &e)
else{ {
//light_sources.push_back(n2pos); continue;
light_sources.insert(n2pos, true); }
} }
} }
/*dstream<<"spreadLight(): Changed block "
<<blockchangecount<<" times"
<<" for "<<from_nodes.size()<<" nodes"
<<std::endl;*/
if(lighted_nodes.size() > 0)
spreadLight(lighted_nodes, modified_blocks);
} }
#endif #endif
@ -1090,6 +1168,13 @@ void Map::timerUpdate(float dtime)
void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks) void Map::deleteSectors(core::list<v2s16> &list, bool only_blocks)
{ {
/*
Wait for caches to be removed before continuing.
This disables the existence of caches while locked
*/
SharedPtr<JMutexAutoLock> cachelock(m_blockcachelock.waitCaches());
core::list<v2s16>::Iterator j; core::list<v2s16>::Iterator j;
for(j=list.begin(); j!=list.end(); j++) for(j=list.begin(); j!=list.end(); j++)
{ {
@ -1215,13 +1300,13 @@ ServerMap::ServerMap(std::string savedir, HMParams hmp, MapParams mp):
// Create master heightmap // Create master heightmap
ValueGenerator *maxgen = ValueGenerator *maxgen =
ValueGenerator::deSerialize(hmp.height_randmax); ValueGenerator::deSerialize(hmp.randmax);
ValueGenerator *factorgen = ValueGenerator *factorgen =
ValueGenerator::deSerialize(hmp.height_randfactor); ValueGenerator::deSerialize(hmp.randfactor);
ValueGenerator *basegen = ValueGenerator *basegen =
ValueGenerator::deSerialize(hmp.height_base); ValueGenerator::deSerialize(hmp.base);
m_heightmap = new UnlimitedHeightmap m_heightmap = new UnlimitedHeightmap
(hmp.heightmap_blocksize, maxgen, factorgen, basegen); (hmp.blocksize, maxgen, factorgen, basegen);
// Set map parameters // Set map parameters
m_params = mp; m_params = mp;
@ -1409,6 +1494,9 @@ MapSector * ServerMap::emergeSector(v2s16 p2d)
SECTOR_OBJECT_TREE_1); SECTOR_OBJECT_TREE_1);
} }
} }
/*
Plant some bushes if sector is pit-like
*/
{ {
// Pitness usually goes at around -0.5...0.5 // Pitness usually goes at around -0.5...0.5
u32 bush_max = 0; u32 bush_max = 0;
@ -1429,6 +1517,22 @@ MapSector * ServerMap::emergeSector(v2s16 p2d)
SECTOR_OBJECT_BUSH_1); SECTOR_OBJECT_BUSH_1);
} }
} }
/*
Add ravine (randomly)
*/
{
if(rand()%10 == 0)
{
s16 s = 6;
s16 x = rand()%(MAP_BLOCKSIZE-s*2-1)+s;
s16 z = rand()%(MAP_BLOCKSIZE-s*2-1)+s;
/*s16 x = 8;
s16 z = 8;*/
s16 y = sector->getGroundHeight(v2s16(x,z))+1;
objects->insert(v3s16(x, y, z),
SECTOR_OBJECT_RAVINE);
}
}
/* /*
Insert to container Insert to container
@ -1533,9 +1637,16 @@ MapBlock * ServerMap::emergeBlock(
} }
// Randomize a bit. This makes dungeons. // Randomize a bit. This makes dungeons.
bool low_block_is_empty = false; /*bool low_block_is_empty = false;
if(rand() % 4 == 0) if(rand() % 4 == 0)
low_block_is_empty = true; low_block_is_empty = true;*/
s32 ued = 4;
bool underground_emptiness[ued*ued*ued];
for(s32 i=0; i<ued*ued*ued; i++)
{
underground_emptiness[i] = ((rand() % 4) == 0);
}
// This is the basic material of what the visible flat ground // This is the basic material of what the visible flat ground
// will consist of // will consist of
@ -1551,9 +1662,7 @@ MapBlock * ServerMap::emergeBlock(
{ {
//dstream<<"emergeBlock: x0="<<x0<<", z0="<<z0<<std::endl; //dstream<<"emergeBlock: x0="<<x0<<", z0="<<z0<<std::endl;
float surface_y_f = sector->getGroundHeight(v2s16(x0,z0)); float surface_y_f = sector->getGroundHeight(v2s16(x0,z0));
assert(surface_y_f > GROUNDHEIGHT_VALID_MINVALUE); assert(surface_y_f > GROUNDHEIGHT_VALID_MINVALUE);
s16 surface_y = surface_y_f; s16 surface_y = surface_y_f;
//avg_ground_y += surface_y; //avg_ground_y += surface_y;
if(surface_y < lowest_ground_y) if(surface_y < lowest_ground_y)
@ -1574,13 +1683,14 @@ MapBlock * ServerMap::emergeBlock(
else else
surface_depth = (1.-(slope-min_slope)/max_slope) * min_slope_depth; surface_depth = (1.-(slope-min_slope)/max_slope) * min_slope_depth;
for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++){ for(s16 y0=0; y0<MAP_BLOCKSIZE; y0++)
{
s16 real_y = block_y * MAP_BLOCKSIZE + y0; s16 real_y = block_y * MAP_BLOCKSIZE + y0;
MapNode n; MapNode n;
/* /*
Calculate lighting Calculate lighting
FIXME: If there are some man-made structures above the NOTE: If there are some man-made structures above the
newly created block, they won't be taken into account. newly created block, they won't be taken into account.
*/ */
if(real_y > surface_y) if(real_y > surface_y)
@ -1589,12 +1699,17 @@ MapBlock * ServerMap::emergeBlock(
Calculate material Calculate material
*/ */
// If node is very low // If node is very low
if(real_y <= surface_y - 10){ if(real_y <= surface_y - 7){
// Create dungeons // Create dungeons
if(low_block_is_empty){ if(underground_emptiness[
ued*ued*(z0*ued/MAP_BLOCKSIZE)
+ued*(y0*ued/MAP_BLOCKSIZE)
+(x0*ued/MAP_BLOCKSIZE)])
{
n.d = MATERIAL_AIR; n.d = MATERIAL_AIR;
} }
else{ else
{
n.d = MATERIAL_STONE; n.d = MATERIAL_STONE;
} }
} }
@ -1603,7 +1718,14 @@ MapBlock * ServerMap::emergeBlock(
n.d = MATERIAL_STONE; n.d = MATERIAL_STONE;
// If node is at or under heightmap y // If node is at or under heightmap y
else if(real_y <= surface_y) else if(real_y <= surface_y)
n.d = material; {
// If under water level, it's mud
if(real_y < WATER_LEVEL)
n.d = MATERIAL_MUD;
// Else it's the main material
else
n.d = material;
}
// If node is over heightmap y // If node is over heightmap y
else{ else{
// If under water level, it's water // If under water level, it's water
@ -1628,11 +1750,21 @@ MapBlock * ServerMap::emergeBlock(
bool is_underground = (block_y+1) * MAP_BLOCKSIZE < lowest_ground_y; bool is_underground = (block_y+1) * MAP_BLOCKSIZE < lowest_ground_y;
block->setIsUnderground(is_underground); block->setIsUnderground(is_underground);
/*
Force lighting update if underground.
This is needed because of ravines.
*/
if(is_underground)
{
lighting_invalidated_blocks[block->getPos()] = block;
}
/* /*
Add some minerals Add some minerals
*/ */
if(is_underground && low_block_is_empty == false) if(is_underground)
{ {
s16 underground_level = lowest_ground_y/MAP_BLOCKSIZE - block_y; s16 underground_level = lowest_ground_y/MAP_BLOCKSIZE - block_y;
for(s16 i=0; i<underground_level*3; i++) for(s16 i=0; i<underground_level*3; i++)
@ -1640,9 +1772,6 @@ MapBlock * ServerMap::emergeBlock(
if(rand()%2 == 0) if(rand()%2 == 0)
{ {
v3s16 cp( v3s16 cp(
/*(rand()%(MAP_BLOCKSIZE-4))+2,
(rand()%(MAP_BLOCKSIZE-4))+2,
(rand()%(MAP_BLOCKSIZE-4))+2*/
(rand()%(MAP_BLOCKSIZE-2))+1, (rand()%(MAP_BLOCKSIZE-2))+1,
(rand()%(MAP_BLOCKSIZE-2))+1, (rand()%(MAP_BLOCKSIZE-2))+1,
(rand()%(MAP_BLOCKSIZE-2))+1 (rand()%(MAP_BLOCKSIZE-2))+1
@ -1656,6 +1785,9 @@ MapBlock * ServerMap::emergeBlock(
for(u16 i=0; i<26; i++) for(u16 i=0; i<26; i++)
{ {
if(!is_ground_material(block->getNode(cp+g_26dirs[i]).d))
continue;
if(rand()%8 == 0) if(rand()%8 == 0)
block->setNode(cp+g_26dirs[i], n); block->setNode(cp+g_26dirs[i], n);
} }
@ -1666,7 +1798,7 @@ MapBlock * ServerMap::emergeBlock(
/* /*
Create a few rats in empty blocks underground Create a few rats in empty blocks underground
*/ */
if(is_underground && low_block_is_empty == true) /*if(is_underground && low_block_is_empty == true)
{ {
//for(u16 i=0; i<2; i++) //for(u16 i=0; i<2; i++)
{ {
@ -1674,42 +1806,54 @@ MapBlock * ServerMap::emergeBlock(
RatObject *obj = new RatObject(NULL, -1, intToFloat(pos)); RatObject *obj = new RatObject(NULL, -1, intToFloat(pos));
block->addObject(obj); block->addObject(obj);
} }
}
/*
TODO: REMOVE
DEBUG
Add some objects to the block for testing.
*/
/*if(p == v3s16(0,0,0))
{
//TestObject *obj = new TestObject(NULL, -1, v3f(BS*8,BS*8,BS*8));
Test2Object *obj = new Test2Object(NULL, -1, v3f(BS*8,BS*15,BS*8));
block->addObject(obj);
}*/ }*/
/*
{
v3s16 pos(8, 11, 8);
SignObject *obj = new SignObject(NULL, -1, intToFloat(pos));
obj->setText("Moicka");
obj->setYaw(45);
block->addObject(obj);
}
{
v3s16 pos(8, 11, 8);
RatObject *obj = new RatObject(NULL, -1, intToFloat(pos));
block->addObject(obj);
}
*/
/* /*
Add block to sector. Add block to sector.
*/ */
sector->insertBlock(block); sector->insertBlock(block);
// An y-wise container if changed blocks /*
Do some interpolation for dungeons
*/
#if 0
{
TimeTaker timer("interpolation", g_device);
MapVoxelManipulator vmanip(this);
v3s16 relpos = block->getPosRelative();
vmanip.interpolate(VoxelArea(relpos-v3s16(1,1,1),
relpos+v3s16(1,1,1)*(MAP_BLOCKSIZE+1)));
/*vmanip.interpolate(VoxelArea(relpos,
relpos+v3s16(1,1,1)*(MAP_BLOCKSIZE-1)));*/
core::map<v3s16, MapBlock*> modified_blocks;
vmanip.blitBack(modified_blocks);
dstream<<"blitBack modified "<<modified_blocks.size()
<<" blocks"<<std::endl;
// Add modified blocks to changed_blocks and lighting_invalidated_blocks
for(core::map<v3s16, MapBlock*>::Iterator
i = modified_blocks.getIterator();
i.atEnd() == false; i++)
{
MapBlock *block = i.getNode()->getValue();
changed_blocks.insert(block->getPos(), block);
//lighting_invalidated_blocks.insert(block->getPos(), block);
}
}
#endif
/*
Sector object stuff
*/
// An y-wise container of changed blocks
core::map<s16, MapBlock*> changed_blocks_sector; core::map<s16, MapBlock*> changed_blocks_sector;
/* /*
@ -1722,6 +1866,7 @@ MapBlock * ServerMap::emergeBlock(
i.atEnd() == false; i++) i.atEnd() == false; i++)
{ {
v3s16 p = i.getNode()->getKey(); v3s16 p = i.getNode()->getKey();
v2s16 p2d(p.X,p.Z);
u8 d = i.getNode()->getValue(); u8 d = i.getNode()->getValue();
//v3s16 p = p_sector - v3s16(0, block_y*MAP_BLOCKSIZE, 0); //v3s16 p = p_sector - v3s16(0, block_y*MAP_BLOCKSIZE, 0);
@ -1795,6 +1940,66 @@ MapBlock * ServerMap::emergeBlock(
objects_to_remove.push_back(p); objects_to_remove.push_back(p);
} }
} }
else if(d == SECTOR_OBJECT_RAVINE)
{
s16 maxdepth = -20;
v3s16 p_min = p + v3s16(-6,maxdepth,-6);
v3s16 p_max = p + v3s16(6,6,6);
if(sector->isValidArea(p_min, p_max,
&changed_blocks_sector))
{
MapNode n;
n.d = MATERIAL_STONE;
MapNode n2;
n2.d = MATERIAL_AIR;
s16 depth = maxdepth + (rand()%10);
s16 z = 0;
s16 minz = -6 - (-2);
s16 maxz = 6 -1;
for(s16 x=-6; x<=6; x++)
{
z += -1 + (rand()%3);
if(z < minz)
z = minz;
if(z > maxz)
z = maxz;
for(s16 y=depth+(rand()%2); y<=6; y++)
{
/*std::cout<<"("<<p2.X<<","<<p2.Y<<","<<p2.Z<<")"
<<std::endl;*/
{
v3s16 p2 = p + v3s16(x,y,z-2);
if(is_ground_material(sector->getNode(p2).d))
sector->setNode(p2, n);
}
{
v3s16 p2 = p + v3s16(x,y,z-1);
if(is_ground_material(sector->getNode(p2).d))
sector->setNode(p2, n2);
}
{
v3s16 p2 = p + v3s16(x,y,z+0);
if(is_ground_material(sector->getNode(p2).d))
sector->setNode(p2, n2);
}
{
v3s16 p2 = p + v3s16(x,y,z+1);
if(is_ground_material(sector->getNode(p2).d))
sector->setNode(p2, n);
}
//if(sector->getNode(p+v3s16(x,y,z+1)).solidness()==2)
//if(p.Y+y <= sector->getGroundHeight(p2d+v2s16(x,z-2))+0.5)
}
}
objects_to_remove.push_back(p);
// Lighting has to be recalculated for this one.
sector->getBlocksInArea(p_min, p_max,
lighting_invalidated_blocks);
}
}
else else
{ {
dstream<<"ServerMap::emergeBlock(): " dstream<<"ServerMap::emergeBlock(): "
@ -1807,7 +2012,7 @@ MapBlock * ServerMap::emergeBlock(
{ {
dstream<<"WARNING: "<<__FUNCTION_NAME dstream<<"WARNING: "<<__FUNCTION_NAME
<<": while inserting object "<<(int)d <<": while inserting object "<<(int)d
<<" to ("<<p.X<<","<<p.Y<<","<<p.Z<<")" <<" to ("<<p.X<<","<<p.Y<<","<<p.Z<<"):"
<<" InvalidPositionException.what()=" <<" InvalidPositionException.what()="
<<e.what()<<std::endl; <<e.what()<<std::endl;
// This is not too fatal and seems to happen sometimes. // This is not too fatal and seems to happen sometimes.

152
src/map.h

@ -26,12 +26,129 @@
#include "mapsector.h" #include "mapsector.h"
#include "constants.h" #include "constants.h"
class InvalidFilenameException : public BaseException class Map;
/*
A cache for short-term fast access to map data
NOTE: This doesn't really make anything more efficient
NOTE: Use VoxelManipulator, if possible
TODO: Get rid of this?
*/
class MapBlockPointerCache : public NodeContainer
{ {
public: public:
InvalidFilenameException(const char *s): MapBlockPointerCache(Map *map);
BaseException(s) ~MapBlockPointerCache();
{}
virtual u16 nodeContainerId() const
{
return NODECONTAINER_ID_MAPBLOCKCACHE;
}
MapBlock * getBlockNoCreate(v3s16 p);
// virtual from NodeContainer
bool isValidPosition(v3s16 p)
{
v3s16 blockpos = getNodeBlockPos(p);
MapBlock *blockref;
try{
blockref = getBlockNoCreate(blockpos);
}
catch(InvalidPositionException &e)
{
return false;
}
return true;
}
// virtual from NodeContainer
MapNode getNode(v3s16 p)
{
v3s16 blockpos = getNodeBlockPos(p);
MapBlock * blockref = getBlockNoCreate(blockpos);
v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
return blockref->getNodeNoCheck(relpos);
}
// virtual from NodeContainer
void setNode(v3s16 p, MapNode & n)
{
v3s16 blockpos = getNodeBlockPos(p);
MapBlock * block = getBlockNoCreate(blockpos);
v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
block->setNodeNoCheck(relpos, n);
m_modified_blocks[blockpos] = block;
}
core::map<v3s16, MapBlock*> m_modified_blocks;
private:
Map *m_map;
core::map<v3s16, MapBlock*> m_blocks;
u32 m_from_cache_count;
u32 m_from_map_count;
};
class CacheLock
{
public:
CacheLock()
{
m_count = 0;
m_count_mutex.Init();
m_cache_mutex.Init();
m_waitcache_mutex.Init();
}
void cacheCreated()
{
JMutexAutoLock waitcachelock(m_waitcache_mutex);
JMutexAutoLock countlock(m_count_mutex);
// If this is the first cache, grab the cache lock
if(m_count == 0)
m_cache_mutex.Lock();
m_count++;
}
void cacheRemoved()
{
JMutexAutoLock countlock(m_count_mutex);
assert(m_count > 0);
m_count--;
// If this is the last one, release the cache lock
if(m_count == 0)
m_cache_mutex.Unlock();
}
/*
This lock should be taken when removing stuff that can be
pointed by the cache.
You'll want to grab this in a SharedPtr.
*/
JMutexAutoLock * waitCaches()
{
JMutexAutoLock waitcachelock(m_waitcache_mutex);
return new JMutexAutoLock(m_cache_mutex);
}
private:
// Count of existing caches
u32 m_count;
JMutex m_count_mutex;
// This is locked always when there are some caches
JMutex m_cache_mutex;
// Locked so that when waitCaches() is called, no more caches are created
JMutex m_waitcache_mutex;
}; };
#define MAPTYPE_BASE 0 #define MAPTYPE_BASE 0
@ -60,6 +177,13 @@ protected:
public: public:
v3s16 drawoffset; // for drawbox() v3s16 drawoffset; // for drawbox()
/*
Used by MapBlockPointerCache.
waitCaches() can be called to remove all caches before continuing
*/
CacheLock m_blockcachelock;
Map(std::ostream &dout); Map(std::ostream &dout);
virtual ~Map(); virtual ~Map();
@ -154,7 +278,7 @@ public:
MapBlock * blockref = getBlockNoCreate(blockpos); MapBlock * blockref = getBlockNoCreate(blockpos);
v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
return blockref->getNode(relpos); return blockref->getNodeNoCheck(relpos);
} }
// virtual from NodeContainer // virtual from NodeContainer
@ -163,7 +287,7 @@ public:
v3s16 blockpos = getNodeBlockPos(p); v3s16 blockpos = getNodeBlockPos(p);
MapBlock * blockref = getBlockNoCreate(blockpos); MapBlock * blockref = getBlockNoCreate(blockpos);
v3s16 relpos = p - blockpos*MAP_BLOCKSIZE; v3s16 relpos = p - blockpos*MAP_BLOCKSIZE;
blockref->setNode(relpos, n); blockref->setNodeNoCheck(relpos, n);
} }
/*MapNode getNodeGenerate(v3s16 p) /*MapNode getNodeGenerate(v3s16 p)
@ -247,15 +371,15 @@ struct HMParams
{ {
HMParams() HMParams()
{ {
heightmap_blocksize = 64; blocksize = 64;
height_randmax = "constant 70.0"; randmax = "constant 70.0";
height_randfactor = "constant 0.6"; randfactor = "constant 0.6";
height_base = "linear 0 80 0"; base = "linear 0 80 0";
} }
s16 heightmap_blocksize; s16 blocksize;
std::string height_randmax; std::string randmax;
std::string height_randfactor; std::string randfactor;
std::string height_base; std::string base;
}; };
// Map parameters // Map parameters

@ -38,7 +38,9 @@ enum
{ {
NODECONTAINER_ID_MAPBLOCK, NODECONTAINER_ID_MAPBLOCK,
NODECONTAINER_ID_MAPSECTOR, NODECONTAINER_ID_MAPSECTOR,
NODECONTAINER_ID_MAP NODECONTAINER_ID_MAP,
NODECONTAINER_ID_MAPBLOCKCACHE,
NODECONTAINER_ID_VOXELMANIPULATOR,
}; };
class NodeContainer class NodeContainer
@ -245,6 +247,35 @@ public:
setNode(p.X, p.Y, p.Z, n); setNode(p.X, p.Y, p.Z, n);
} }
/*
Non-checking variants of the above
*/
MapNode getNodeNoCheck(s16 x, s16 y, s16 z)
{
if(data == NULL)
throw InvalidPositionException();
return data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x];
}
MapNode getNodeNoCheck(v3s16 p)
{
return getNodeNoCheck(p.X, p.Y, p.Z);
}
void setNodeNoCheck(s16 x, s16 y, s16 z, MapNode & n)
{
if(data == NULL)
throw InvalidPositionException();
data[z*MAP_BLOCKSIZE*MAP_BLOCKSIZE + y*MAP_BLOCKSIZE + x] = n;
setChangedFlag();
}
void setNodeNoCheck(v3s16 p, MapNode & n)
{
setNodeNoCheck(p.X, p.Y, p.Z, n);
}
/* /*
These functions consult the parent container if the position These functions consult the parent container if the position
is not valid on this MapBlock. is not valid on this MapBlock.

@ -17,10 +17,25 @@
#define MATERIALS_COUNT 256 #define MATERIALS_COUNT 256
// This is completely ignored. It doesn't create faces with anything. /*
Ignored node.
param is used for custom information in special containers,
like VoxelManipulator.
Anything that stores MapNodes doesn't have to preserve parameters
associated with this material.
Doesn't create faces with anything and is considered being
out-of-map in the game map.
*/
#define MATERIAL_IGNORE 255 #define MATERIAL_IGNORE 255
// This is the common material through which the player can walk #define MATERIAL_IGNORE_DEFAULT_PARAM 0
// and which is transparent to light
/*
The common material through which the player can walk and which
is transparent to light
*/
#define MATERIAL_AIR 254 #define MATERIAL_AIR 254
/* /*
@ -63,6 +78,8 @@ enum Material
MATERIAL_GRASS_FOOTSTEPS, MATERIAL_GRASS_FOOTSTEPS,
MATERIAL_MESE, MATERIAL_MESE,
MATERIAL_MUD,
// This is set to the number of the actual values in this enum // This is set to the number of the actual values in this enum
USEFUL_MATERIAL_COUNT USEFUL_MATERIAL_COUNT
@ -126,6 +143,21 @@ inline u8 face_materials(u8 m1, u8 m2)
return 2; return 2;
} }
/*
Returns true for materials that form the base ground that
follows the main heightmap
*/
inline bool is_ground_material(u8 m)
{
return(
m == MATERIAL_STONE ||
m == MATERIAL_GRASS ||
m == MATERIAL_GRASS_FOOTSTEPS ||
m == MATERIAL_MESE ||
m == MATERIAL_MUD
);
}
struct MapNode struct MapNode
{ {
//TODO: block type to differ from material //TODO: block type to differ from material
@ -133,9 +165,6 @@ struct MapNode
// block type // block type
u8 d; u8 d;
// Removed because light is now stored in param for air
// f32 light;
/* /*
Misc parameter. Initialized to 0. Misc parameter. Initialized to 0.
- For light_propagates() blocks, this is light intensity, - For light_propagates() blocks, this is light intensity,
@ -155,6 +184,11 @@ struct MapNode
param = a_param; param = a_param;
} }
bool operator==(const MapNode &other)
{
return (d == other.d && param == other.param);
}
bool light_propagates() bool light_propagates()
{ {
return light_propagates_material(d); return light_propagates_material(d);

@ -20,6 +20,7 @@
#define SECTOR_OBJECT_TEST 0 #define SECTOR_OBJECT_TEST 0
#define SECTOR_OBJECT_TREE_1 1 #define SECTOR_OBJECT_TREE_1 1
#define SECTOR_OBJECT_BUSH_1 2 #define SECTOR_OBJECT_BUSH_1 2
#define SECTOR_OBJECT_RAVINE 3
#define MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT 4 #define MAPSECTOR_FIXEDHEIGHTMAPS_MAXCOUNT 4

@ -178,7 +178,10 @@ void * EmergeThread::Thread()
modified_blocks.insert(block->getPos(), block); modified_blocks.insert(block->getPos(), block);
} }
//TimeTaker timer("** updateLighting", g_device); /*dstream<<"lighting "<<lighting_invalidated_blocks.size()
<<" blocks"<<std::endl;
TimeTaker timer("** updateLighting", g_device);*/
// Update lighting without locking the environment mutex, // Update lighting without locking the environment mutex,
// add modified blocks to changed blocks // add modified blocks to changed blocks
map.updateLighting(lighting_invalidated_blocks, modified_blocks); map.updateLighting(lighting_invalidated_blocks, modified_blocks);
@ -222,11 +225,11 @@ void * EmergeThread::Thread()
client->SetBlocksNotSent(modified_blocks); client->SetBlocksNotSent(modified_blocks);
} }
if(q->peer_ids.find(client->peer_id) != NULL) /*if(q->peer_ids.find(client->peer_id) != NULL)
{ {
// Decrement emerge queue count of client // Decrement emerge queue count of client
client->BlockEmerged(); client->BlockEmerged();
} }*/
} }
} }
@ -246,282 +249,6 @@ void * EmergeThread::Thread()
return NULL; return NULL;
} }
#if 0
void RemoteClient::SendBlocks(Server *server, float dtime)
{
DSTACK(__FUNCTION_NAME);
/*
Find what blocks to send to the client next, and send them.
Throttling is based on limiting the amount of blocks "flying"
at a given time.
*/
// Can't send anything without knowing version
if(serialization_version == SER_FMT_VER_INVALID)
{
dstream<<"RemoteClient::SendBlocks(): Not sending, no version."
<<std::endl;
return;
}
{
JMutexAutoLock lock(m_blocks_sending_mutex);
if(m_blocks_sending.size() >= MAX_SIMULTANEOUS_BLOCK_SENDS)
{
//dstream<<"Not sending any blocks, Queue full."<<std::endl;
return;
}
}
Player *player = server->m_env.getPlayer(peer_id);
v3f playerpos = player->getPosition();
v3f playerspeed = player->getSpeed();
v3s16 center_nodepos = floatToInt(playerpos);
v3s16 center = getNodeBlockPos(center_nodepos);
/*
Get the starting value of the block finder radius.
*/
s16 last_nearest_unsent_d;
s16 d_start;
{
JMutexAutoLock lock(m_blocks_sent_mutex);
if(m_last_center != center)
{
m_nearest_unsent_d = 0;
m_last_center = center;
}
static float reset_counter = 0;
reset_counter += dtime;
if(reset_counter > 5.0)
{
reset_counter = 0;
m_nearest_unsent_d = 0;
}
last_nearest_unsent_d = m_nearest_unsent_d;
d_start = m_nearest_unsent_d;
}
u16 maximum_simultaneous_block_sends = MAX_SIMULTANEOUS_BLOCK_SENDS;
{
SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock());
m_time_from_building.m_value += dtime;
/*
Check the time from last addNode/removeNode.
Decrease send rate if player is building stuff.
*/
if(m_time_from_building.m_value
< FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING)
{
maximum_simultaneous_block_sends
= LIMITED_MAX_SIMULTANEOUS_BLOCK_SENDS;
}
}
// Serialization version used
//u8 ser_version = serialization_version;
//bool has_incomplete_blocks = false;
/*
TODO: Get this from somewhere
*/
//s16 d_max = 7;
s16 d_max = 8;
//TODO: Get this from somewhere (probably a bigger value)
s16 d_max_gen = 5;
//dstream<<"Starting from "<<d_start<<std::endl;
for(s16 d = d_start; d <= d_max; d++)
{
//dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl;
//if(has_incomplete_blocks == false)
{
JMutexAutoLock lock(m_blocks_sent_mutex);
/*
If m_nearest_unsent_d was changed by the EmergeThread
(it can change it to 0 through SetBlockNotSent),
update our d to it.
Else update m_nearest_unsent_d
*/
if(m_nearest_unsent_d != last_nearest_unsent_d)
{
d = m_nearest_unsent_d;
}
else
{
m_nearest_unsent_d = d;
}
last_nearest_unsent_d = m_nearest_unsent_d;
}
/*
Get the border/face dot coordinates of a "d-radiused"
box
*/
core::list<v3s16> list;
getFacePositions(list, d);
core::list<v3s16>::Iterator li;
for(li=list.begin(); li!=list.end(); li++)
{
v3s16 p = *li + center;
/*
Send throttling
- Don't allow too many simultaneous transfers
Also, don't send blocks that are already flying.
*/
{
JMutexAutoLock lock(m_blocks_sending_mutex);
if(m_blocks_sending.size()
>= maximum_simultaneous_block_sends)
{
/*dstream<<"Not sending more blocks. Queue full. "
<<m_blocks_sending.size()
<<std::endl;*/
return;
}
if(m_blocks_sending.find(p) != NULL)
continue;
}
/*
Do not go over-limit
*/
if(p.X < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
|| p.X > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
|| p.Y < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
|| p.Y > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
|| p.Z < -MAP_GENERATION_LIMIT / MAP_BLOCKSIZE
|| p.Z > MAP_GENERATION_LIMIT / MAP_BLOCKSIZE)
continue;
bool generate = d <= d_max_gen;
// Limit the generating area vertically to half
if(abs(p.Y - center.Y) > d_max_gen / 2)
generate = false;
/*
Don't send already sent blocks
*/
{
JMutexAutoLock lock(m_blocks_sent_mutex);
if(m_blocks_sent.find(p) != NULL)
continue;
}
/*
Check if map has this block
*/
MapBlock *block = NULL;
try
{
block = server->m_env.getMap().getBlockNoCreate(p);
}
catch(InvalidPositionException &e)
{
}
bool surely_not_found_on_disk = false;
if(block != NULL)
{
/*if(block->isIncomplete())
{
has_incomplete_blocks = true;
continue;
}*/
if(block->isDummy())
{
surely_not_found_on_disk = true;
}
}
/*
If block has been marked to not exist on disk (dummy)
and generating new ones is not wanted, skip block. TODO
*/
if(generate == false && surely_not_found_on_disk == true)
{
// get next one.
continue;
}
/*
Add inexistent block to emerge queue.
*/
if(block == NULL || surely_not_found_on_disk)
{
// Block not found.
SharedPtr<JMutexAutoLock> lock
(m_num_blocks_in_emerge_queue.getLock());
//TODO: Get value from somewhere
//TODO: Balance between clients
//if(server->m_emerge_queue.size() < 1)
// Allow only one block in emerge queue
if(m_num_blocks_in_emerge_queue.m_value == 0)
{
// Add it to the emerge queue and trigger the thread
u8 flags = 0;
if(generate == false)
flags |= TOSERVER_GETBLOCK_FLAG_OPTIONAL;
{
m_num_blocks_in_emerge_queue.m_value++;
}
server->m_emerge_queue.addBlock(peer_id, p, flags);
server->m_emergethread.trigger();
}
// get next one.
continue;
}
/*
Send block
*/
/*dstream<<"RemoteClient::SendBlocks(): d="<<d<<", p="
<<"("<<p.X<<","<<p.Y<<","<<p.Z<<")"
<<" sending queue size: "<<m_blocks_sending.size()<<std::endl;*/
server->SendBlockNoLock(peer_id, block, serialization_version);
/*
Add to history
*/
SentBlock(p);
}
}
// Don't add anything here. The loop breaks by returning.
}
#endif // backup of SendBlocks
void RemoteClient::GetNextBlocks(Server *server, float dtime, void RemoteClient::GetNextBlocks(Server *server, float dtime,
core::array<PrioritySortedBlockTransfer> &dest) core::array<PrioritySortedBlockTransfer> &dest)
{ {
@ -531,7 +258,8 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
{ {
JMutexAutoLock lock(m_blocks_sending_mutex); JMutexAutoLock lock(m_blocks_sending_mutex);
if(m_blocks_sending.size() >= MAX_SIMULTANEOUS_BLOCK_SENDS) if(m_blocks_sending.size() >= g_settings.getU16
("max_simultaneous_block_sends_per_client"))
{ {
//dstream<<"Not sending any blocks, Queue full."<<std::endl; //dstream<<"Not sending any blocks, Queue full."<<std::endl;
return; return;
@ -574,15 +302,17 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
d_start = m_nearest_unsent_d; d_start = m_nearest_unsent_d;
} }
u16 maximum_simultaneous_block_sends = MAX_SIMULTANEOUS_BLOCK_SENDS; u16 maximum_simultaneous_block_sends = g_settings.getU16
("max_simultaneous_block_sends_per_client");
/*
Check the time from last addNode/removeNode.
Decrease send rate if player is building stuff.
*/
{ {
SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock()); SharedPtr<JMutexAutoLock> lock(m_time_from_building.getLock());
m_time_from_building.m_value += dtime; m_time_from_building.m_value += dtime;
/*
Check the time from last addNode/removeNode.
Decrease send rate if player is building stuff.
*/
if(m_time_from_building.m_value if(m_time_from_building.m_value
< FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING) < FULL_BLOCK_SEND_ENABLE_MIN_TIME_FROM_BUILDING)
{ {
@ -646,9 +376,11 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
/* /*
Send throttling Send throttling
- Don't allow too many simultaneous transfers - Don't allow too many simultaneous transfers
- EXCEPT when the blocks are very close
Also, don't send blocks that are already flying. Also, don't send blocks that are already flying.
*/ */
if(d >= BLOCK_SEND_DISABLE_LIMITS_MAX_D)
{ {
JMutexAutoLock lock(m_blocks_sending_mutex); JMutexAutoLock lock(m_blocks_sending_mutex);
@ -722,7 +454,7 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
/* /*
If block has been marked to not exist on disk (dummy) If block has been marked to not exist on disk (dummy)
and generating new ones is not wanted, skip block. TODO and generating new ones is not wanted, skip block.
*/ */
if(generate == false && surely_not_found_on_disk == true) if(generate == false && surely_not_found_on_disk == true)
{ {
@ -735,16 +467,12 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
*/ */
if(block == NULL || surely_not_found_on_disk) if(block == NULL || surely_not_found_on_disk)
{ {
// Block not found. /*SharedPtr<JMutexAutoLock> lock
SharedPtr<JMutexAutoLock> lock (m_num_blocks_in_emerge_queue.getLock());*/
(m_num_blocks_in_emerge_queue.getLock());
//TODO: Get value from somewhere //TODO: Get value from somewhere
//TODO: Balance between clients
//if(server->m_emerge_queue.size() < 1)
// Allow only one block in emerge queue // Allow only one block in emerge queue
if(m_num_blocks_in_emerge_queue.m_value == 0) if(server->m_emerge_queue.peerItemCount(peer_id) < 1)
{ {
// Add it to the emerge queue and trigger the thread // Add it to the emerge queue and trigger the thread
@ -752,10 +480,6 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
if(generate == false) if(generate == false)
flags |= TOSERVER_GETBLOCK_FLAG_OPTIONAL; flags |= TOSERVER_GETBLOCK_FLAG_OPTIONAL;
{
m_num_blocks_in_emerge_queue.m_value++;
}
server->m_emerge_queue.addBlock(peer_id, p, flags); server->m_emerge_queue.addBlock(peer_id, p, flags);
server->m_emergethread.trigger(); server->m_emergethread.trigger();
} }
@ -880,7 +604,7 @@ void RemoteClient::SendObjectData(
v3s16 center = getNodeBlockPos(center_nodepos); v3s16 center = getNodeBlockPos(center_nodepos);
//s16 d_max = ACTIVE_OBJECT_D_BLOCKS; //s16 d_max = ACTIVE_OBJECT_D_BLOCKS;
s16 d_max = server->m_active_object_range; s16 d_max = g_settings.getS16("active_object_range");
// Number of blocks whose objects were written to bos // Number of blocks whose objects were written to bos
u16 blockcount = 0; u16 blockcount = 0;
@ -956,9 +680,9 @@ void RemoteClient::SendObjectData(
// Fetch the block only if it is on disk. // Fetch the block only if it is on disk.
// Grab and increment counter // Grab and increment counter
SharedPtr<JMutexAutoLock> lock /*SharedPtr<JMutexAutoLock> lock
(m_num_blocks_in_emerge_queue.getLock()); (m_num_blocks_in_emerge_queue.getLock());
m_num_blocks_in_emerge_queue.m_value++; m_num_blocks_in_emerge_queue.m_value++;*/
// Add to queue as an anonymous fetch from disk // Add to queue as an anonymous fetch from disk
u8 flags = TOSERVER_GETBLOCK_FLAG_OPTIONAL; u8 flags = TOSERVER_GETBLOCK_FLAG_OPTIONAL;
@ -1072,12 +796,12 @@ void RemoteClient::SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks)
} }
} }
void RemoteClient::BlockEmerged() /*void RemoteClient::BlockEmerged()
{ {
SharedPtr<JMutexAutoLock> lock(m_num_blocks_in_emerge_queue.getLock()); SharedPtr<JMutexAutoLock> lock(m_num_blocks_in_emerge_queue.getLock());
assert(m_num_blocks_in_emerge_queue.m_value > 0); assert(m_num_blocks_in_emerge_queue.m_value > 0);
m_num_blocks_in_emerge_queue.m_value--; m_num_blocks_in_emerge_queue.m_value--;
} }*/
/*void RemoteClient::RunSendingTimeouts(float dtime, float timeout) /*void RemoteClient::RunSendingTimeouts(float dtime, float timeout)
{ {
@ -1145,19 +869,13 @@ u32 PIChecksum(core::list<PlayerInfo> &l)
Server::Server( Server::Server(
std::string mapsavedir, std::string mapsavedir,
bool creative_mode,
HMParams hm_params, HMParams hm_params,
MapParams map_params, MapParams map_params
float objectdata_interval,
u16 active_object_range
): ):
m_env(new ServerMap(mapsavedir, hm_params, map_params), dout_server), m_env(new ServerMap(mapsavedir, hm_params, map_params), dout_server),
m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this), m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
m_thread(this), m_thread(this),
m_emergethread(this), m_emergethread(this)
m_creative_mode(creative_mode),
m_objectdata_interval(objectdata_interval),
m_active_object_range(active_object_range)
{ {
m_env_mutex.Init(); m_env_mutex.Init();
m_con_mutex.Init(); m_con_mutex.Init();
@ -1196,7 +914,7 @@ void Server::start(unsigned short port)
m_thread.stop(); m_thread.stop();
// Initialize connection // Initialize connection
m_con.setTimeoutMs(50); m_con.setTimeoutMs(30);
m_con.Serve(port); m_con.Serve(port);
// Start thread // Start thread
@ -1287,7 +1005,7 @@ void Server::AsyncRunStep()
// Run time- and client- related stuff // Run time- and client- related stuff
// NOTE: If you intend to add something here, check that it // NOTE: If you intend to add something here, check that it
// doesn't fit in RemoteClient::SendBlocks for example. // doesn't fit in RemoteClient::GetNextBlocks for example.
/*{ /*{
// Clients are behind connection lock // Clients are behind connection lock
JMutexAutoLock lock(m_con_mutex); JMutexAutoLock lock(m_con_mutex);
@ -1309,7 +1027,7 @@ void Server::AsyncRunStep()
{ {
static float counter = 0.0; static float counter = 0.0;
counter += dtime; counter += dtime;
if(counter >= m_objectdata_interval) if(counter >= g_settings.getFloat("objectdata_interval"))
{ {
JMutexAutoLock lock1(m_env_mutex); JMutexAutoLock lock1(m_env_mutex);
JMutexAutoLock lock2(m_con_mutex); JMutexAutoLock lock2(m_con_mutex);
@ -1318,9 +1036,22 @@ void Server::AsyncRunStep()
counter = 0.0; counter = 0.0;
} }
} }
// Trigger emergethread (it gets somehow gets to a
// non-triggered but bysy state sometimes)
{
static float counter = 0.0;
counter += dtime;
if(counter >= 2.0)
{
counter = 0.0;
m_emergethread.trigger();
}
}
// Save map
{ {
// Save map
static float counter = 0.0; static float counter = 0.0;
counter += dtime; counter += dtime;
if(counter >= SERVER_MAP_SAVE_INTERVAL) if(counter >= SERVER_MAP_SAVE_INTERVAL)
@ -1619,7 +1350,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
// Left click // Left click
if(button == 0) if(button == 0)
{ {
if(m_creative_mode == false) if(g_settings.getBool("creative_mode") == false)
{ {
// Skip if inventory has no free space // Skip if inventory has no free space
@ -1684,8 +1415,6 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
{ {
return; return;
} }
// Otherwise remove it
m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
} }
catch(InvalidPositionException &e) catch(InvalidPositionException &e)
{ {
@ -1707,7 +1436,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
// Send as reliable // Send as reliable
m_con.SendToAll(0, reply, true); m_con.SendToAll(0, reply, true);
if(m_creative_mode == false) if(g_settings.getBool("creative_mode") == false)
{ {
// Add to inventory and send inventory // Add to inventory and send inventory
InventoryItem *item = new MaterialItem(material, 1); InventoryItem *item = new MaterialItem(material, 1);
@ -1715,6 +1444,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
SendInventory(player->peer_id); SendInventory(player->peer_id);
} }
/*
Remove the node
(this takes some time so it is done after the quick stuff)
*/
m_env.getMap().removeNodeAndUpdate(p_under, modified_blocks);
} // button == 0 } // button == 0
/* /*
Right button places blocks and stuff Right button places blocks and stuff
@ -1744,9 +1479,6 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
MapNode n2 = m_env.getMap().getNode(p_over); MapNode n2 = m_env.getMap().getNode(p_over);
if(n2.d != MATERIAL_AIR) if(n2.d != MATERIAL_AIR)
return; return;
core::map<v3s16, MapBlock*> modified_blocks;
m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
} }
catch(InvalidPositionException &e) catch(InvalidPositionException &e)
{ {
@ -1758,7 +1490,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
// Reset build time counter // Reset build time counter
getClient(peer->id)->m_time_from_building.set(0.0); getClient(peer->id)->m_time_from_building.set(0.0);
if(m_creative_mode == false) if(g_settings.getBool("creative_mode") == false)
{ {
// Remove from inventory and send inventory // Remove from inventory and send inventory
if(mitem->getCount() == 1) if(mitem->getCount() == 1)
@ -1779,6 +1511,14 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
n.serialize(&reply[8], peer_ser_ver); n.serialize(&reply[8], peer_ser_ver);
// Send as reliable // Send as reliable
m_con.SendToAll(0, reply, true); m_con.SendToAll(0, reply, true);
/*
Add node.
This takes some time so it is done after the quick stuff
*/
core::map<v3s16, MapBlock*> modified_blocks;
m_env.getMap().addNodeAndUpdate(p_over, n, modified_blocks);
} }
/* /*
Handle block object items Handle block object items
@ -1828,7 +1568,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
//dout_server<<"Placed object"<<std::endl; //dout_server<<"Placed object"<<std::endl;
if(m_creative_mode == false) if(g_settings.getBool("creative_mode") == false)
{ {
// Remove from inventory and send inventory // Remove from inventory and send inventory
player->inventory.deleteItem(item_i); player->inventory.deleteItem(item_i);
@ -2168,7 +1908,7 @@ void Server::peerAdded(con::Peer *peer)
Add stuff to inventory Add stuff to inventory
*/ */
if(m_creative_mode) if(g_settings.getBool("creative_mode"))
{ {
// Give all materials // Give all materials
assert(USEFUL_MATERIAL_COUNT <= PLAYER_INVENTORY_SIZE); assert(USEFUL_MATERIAL_COUNT <= PLAYER_INVENTORY_SIZE);
@ -2327,35 +2067,6 @@ void Server::SendInventory(u16 peer_id)
m_con.Send(peer_id, 0, data, true); m_con.Send(peer_id, 0, data, true);
} }
#if 0
void Server::SendBlocks(float dtime)
{
DSTACK(__FUNCTION_NAME);
//dstream<<"Server::SendBlocks(): BEGIN"<<std::endl;
JMutexAutoLock envlock(m_env_mutex);
JMutexAutoLock conlock(m_con_mutex);
for(core::map<u16, RemoteClient*>::Iterator
i = m_clients.getIterator();
i.atEnd() == false; i++)
{
RemoteClient *client = i.getNode()->getValue();
assert(client->peer_id == i.getNode()->getKey());
if(client->serialization_version == SER_FMT_VER_INVALID)
continue;
//dstream<<"Server::SendBlocks(): sending blocks for client "<<client->peer_id<<std::endl;
//u16 peer_id = client->peer_id;
client->SendBlocks(this, dtime);
}
//dstream<<"Server::SendBlocks(): END"<<std::endl;
}
#endif
void Server::SendBlocks(float dtime) void Server::SendBlocks(float dtime)
{ {
DSTACK(__FUNCTION_NAME); DSTACK(__FUNCTION_NAME);
@ -2390,8 +2101,9 @@ void Server::SendBlocks(float dtime)
for(u32 i=0; i<queue.size(); i++) for(u32 i=0; i<queue.size(); i++)
{ {
//TODO: Calculate value dynamically //TODO: Calculate limit dynamically
if(total_sending >= MAX_SIMULTANEOUS_BLOCK_SENDS_SERVER_TOTAL) if(total_sending >= g_settings.getS32
("max_simultaneous_block_sends_server_total"))
break; break;
PrioritySortedBlockTransfer q = queue[i]; PrioritySortedBlockTransfer q = queue[i];

@ -102,6 +102,23 @@ public:
JMutexAutoLock lock(m_mutex); JMutexAutoLock lock(m_mutex);
return m_queue.size(); return m_queue.size();
} }
u32 peerItemCount(u16 peer_id)
{
JMutexAutoLock lock(m_mutex);
u32 count = 0;
core::list<QueuedBlockEmerge*>::Iterator i;
for(i=m_queue.begin(); i!=m_queue.end(); i++)
{
QueuedBlockEmerge *q = *i;
if(q->peer_ids.find(peer_id) != NULL)
count++;
}
return count;
}
private: private:
core::list<QueuedBlockEmerge*> m_queue; core::list<QueuedBlockEmerge*> m_queue;
@ -237,8 +254,8 @@ public:
u8 pending_serialization_version; u8 pending_serialization_version;
RemoteClient(): RemoteClient():
m_time_from_building(0.0), m_time_from_building(0.0)
m_num_blocks_in_emerge_queue(0) //m_num_blocks_in_emerge_queue(0)
{ {
peer_id = 0; peer_id = 0;
serialization_version = SER_FMT_VER_INVALID; serialization_version = SER_FMT_VER_INVALID;
@ -276,7 +293,7 @@ public:
void SetBlockNotSent(v3s16 p); void SetBlockNotSent(v3s16 p);
void SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks); void SetBlocksNotSent(core::map<v3s16, MapBlock*> &blocks);
void BlockEmerged(); //void BlockEmerged();
/*bool IsSendingBlock(v3s16 p) /*bool IsSendingBlock(v3s16 p)
{ {
@ -300,8 +317,8 @@ public:
JMutexAutoLock l2(m_blocks_sent_mutex); JMutexAutoLock l2(m_blocks_sent_mutex);
JMutexAutoLock l3(m_blocks_sending_mutex); JMutexAutoLock l3(m_blocks_sending_mutex);
o<<"RemoteClient "<<peer_id<<": " o<<"RemoteClient "<<peer_id<<": "
<<"m_num_blocks_in_emerge_queue=" /*<<"m_num_blocks_in_emerge_queue="
<<m_num_blocks_in_emerge_queue.get() <<m_num_blocks_in_emerge_queue.get()*/
<<", m_blocks_sent.size()="<<m_blocks_sent.size() <<", m_blocks_sent.size()="<<m_blocks_sent.size()
<<", m_blocks_sending.size()="<<m_blocks_sending.size() <<", m_blocks_sending.size()="<<m_blocks_sending.size()
<<", m_nearest_unsent_d="<<m_nearest_unsent_d <<", m_nearest_unsent_d="<<m_nearest_unsent_d
@ -321,10 +338,11 @@ private:
*/ */
//TODO: core::map<v3s16, MapBlock*> m_active_blocks //TODO: core::map<v3s16, MapBlock*> m_active_blocks
//NOTE: Not here, it should be server-wide!
// Number of blocks in the emerge queue that have this client as // Number of blocks in the emerge queue that have this client as
// a receiver. Used for throttling network usage. // a receiver. Used for throttling network usage.
MutexedVariable<s16> m_num_blocks_in_emerge_queue; //MutexedVariable<s16> m_num_blocks_in_emerge_queue;
/* /*
Blocks that have been sent to client. Blocks that have been sent to client.
@ -367,17 +385,17 @@ public:
NOTE: Every public method should be thread-safe NOTE: Every public method should be thread-safe
*/ */
Server( Server(
std::string mapsavedir, std::string mapsavedir,
bool creative_mode, HMParams hm_params,
HMParams hm_params, MapParams map_params
MapParams map_params, );
float objectdata_inverval,
u16 active_object_range
);
~Server(); ~Server();
void start(unsigned short port); void start(unsigned short port);
void stop(); void stop();
// This is mainly a way to pass the time to the server.
// Actual processing is done in an another thread.
void step(float dtime); void step(float dtime);
// This is run by ServerThread and does the actual processing
void AsyncRunStep(); void AsyncRunStep();
void Receive(); void Receive();
void ProcessData(u8 *data, u32 datasize, u16 peer_id); void ProcessData(u8 *data, u32 datasize, u16 peer_id);
@ -387,7 +405,6 @@ public:
// Environment and Connection must be locked when called // Environment and Connection must be locked when called
void SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver); void SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver);
//void SendBlock(u16 peer_id, MapBlock *block, u8 ver);
//TODO: Sending of many blocks in a single packet //TODO: Sending of many blocks in a single packet
// Environment and Connection must be locked when called // Environment and Connection must be locked when called
@ -431,11 +448,6 @@ private:
BlockEmergeQueue m_emerge_queue; BlockEmergeQueue m_emerge_queue;
// Settings
bool m_creative_mode;
float m_objectdata_interval;
u16 m_active_object_range;
friend class EmergeThread; friend class EmergeThread;
friend class RemoteClient; friend class RemoteClient;
}; };

@ -164,7 +164,9 @@ void UDPSocket::Bind(unsigned short port)
if(bind(m_handle, (const sockaddr*)&address, sizeof(sockaddr_in)) < 0) if(bind(m_handle, (const sockaddr*)&address, sizeof(sockaddr_in)) < 0)
{ {
#ifndef DISABLE_ERRNO
dstream<<(int)m_handle<<": Bind failed: "<<strerror(errno)<<std::endl; dstream<<(int)m_handle<<": Bind failed: "<<strerror(errno)<<std::endl;
#endif
throw SocketException("Failed to bind socket"); throw SocketException("Failed to bind socket");
} }
} }
@ -291,7 +293,9 @@ bool UDPSocket::WaitData(int timeout_ms)
} }
else if(result < 0){ else if(result < 0){
// Error // Error
#ifndef DISABLE_ERRNO
dstream<<(int)m_handle<<": Select failed: "<<strerror(errno)<<std::endl; dstream<<(int)m_handle<<": Select failed: "<<strerror(errno)<<std::endl;
#endif
#ifdef _WIN32 #ifdef _WIN32
dstream<<(int)m_handle<<": WSAGetLastError()="<<WSAGetLastError()<<std::endl; dstream<<(int)m_handle<<": WSAGetLastError()="<<WSAGetLastError()<<std::endl;
#endif #endif

@ -1,6 +1,5 @@
#include "test.h" #include "test.h"
#include "common_irrlicht.h" #include "common_irrlicht.h"
#include "debug.h" #include "debug.h"
#include "map.h" #include "map.h"
#include "player.h" #include "player.h"
@ -10,6 +9,7 @@
#include "connection.h" #include "connection.h"
#include "utility.h" #include "utility.h"
#include "serialization.h" #include "serialization.h"
#include "voxel.h"
#include <sstream> #include <sstream>
#ifdef _WIN32 #ifdef _WIN32
@ -125,6 +125,45 @@ struct TestMapNode
} }
}; };
struct TestVoxelManipulator
{
void Run()
{
VoxelArea a(v3s16(-1,-1,-1), v3s16(1,1,1));
assert(a.index(0,0,0) == 1*3*3 + 1*3 + 1);
assert(a.index(-1,-1,-1) == 0);
VoxelManipulator v;
v.print(dstream);
dstream<<"*** Setting (-1,0,-1)=2 ***"<<std::endl;
//v[v3s16(-1,0,-1)] = MapNode(2);
v[v3s16(-1,0,-1)].d = 2;
v.print(dstream);
assert(v[v3s16(-1,0,-1)].d == 2);
dstream<<"*** Reading from inexistent (0,0,-1) ***"<<std::endl;
assert(v[v3s16(0,0,-1)].d == MATERIAL_IGNORE);
v.print(dstream);
dstream<<"*** Adding area ***"<<std::endl;
v.addArea(a);
v.print(dstream);
assert(v[v3s16(-1,0,-1)].d == 2);
assert(v[v3s16(0,1,1)].d == MATERIAL_IGNORE);
}
};
struct TestMapBlock struct TestMapBlock
{ {
class TC : public NodeContainer class TC : public NodeContainer
@ -906,6 +945,7 @@ void run_tests()
TEST(TestUtilities); TEST(TestUtilities);
TEST(TestCompress); TEST(TestCompress);
TEST(TestMapNode); TEST(TestMapNode);
TEST(TestVoxelManipulator);
TEST(TestMapBlock); TEST(TestMapBlock);
TEST(TestMapSector); TEST(TestMapSector);
TEST(TestHeightmap); TEST(TestHeightmap);

@ -8,8 +8,11 @@
#include "common_irrlicht.h" #include "common_irrlicht.h"
#include "debug.h" #include "debug.h"
#include "strfnd.h" #include "strfnd.h"
#include "exceptions.h"
#include <iostream> #include <iostream>
#include <fstream>
#include <string> #include <string>
#include <sstream>
extern const v3s16 g_26dirs[26]; extern const v3s16 g_26dirs[26];
@ -613,5 +616,157 @@ inline s32 stoi(std::string s, s32 min, s32 max)
return i; return i;
} }
inline s32 stoi(std::string s)
{
return atoi(s.c_str());
}
/*
Config stuff
*/
class Settings
{
public:
// Returns false on EOF
bool parseConfigObject(std::istream &is)
{
if(is.eof())
return false;
// NOTE: This function will be expanded to allow multi-line settings
std::string line;
std::getline(is, line);
//dstream<<"got line: \""<<line<<"\""<<std::endl;
std::string trimmedline = trim(line);
// Ignore comments
if(trimmedline[0] == '#')
return true;
//dstream<<"trimmedline=\""<<trimmedline<<"\""<<std::endl;
Strfnd sf(trim(line));
std::string name = sf.next("=");
name = trim(name);
if(name == "")
return true;
std::string value = sf.next("\n");
value = trim(value);
dstream<<"Config name=\""<<name<<"\" value=\""
<<value<<"\""<<std::endl;
m_settings[name] = value;
return true;
}
// Returns true on success
bool readConfigFile(const char *filename)
{
std::ifstream is(filename);
if(is.good() == false)
{
dstream<<"Error opening configuration file: "
<<filename<<std::endl;
return false;
}
dstream<<"Parsing configuration file: "
<<filename<<std::endl;
while(parseConfigObject(is));
return true;
}
void set(std::string name, std::string value)
{
m_settings[name] = value;
}
std::string get(std::string name)
{
core::map<std::string, std::string>::Node *n;
n = m_settings.find(name);
if(n == NULL)
throw SettingNotFoundException("Setting not found");
return n->getValue();
}
bool getBool(std::string name)
{
return is_yes(get(name));
}
// Asks if empty
bool getBoolAsk(std::string name, std::string question, bool def)
{
std::string s = get(name);
if(s != "")
return is_yes(s);
char templine[10];
std::cout<<question<<" [y/N]: ";
std::cin.getline(templine, 10);
s = templine;
if(s == "")
return def;
return is_yes(s);
}
float getFloat(std::string name)
{
float f;
std::istringstream vis(get(name));
vis>>f;
return f;
}
u16 getU16(std::string name)
{
return stoi(get(name), 0, 65535);
}
u16 getU16Ask(std::string name, std::string question, u16 def)
{
std::string s = get(name);
if(s != "")
return stoi(s, 0, 65535);
char templine[10];
std::cout<<question<<" ["<<def<<"]: ";
std::cin.getline(templine, 10);
s = templine;
if(s == "")
return def;
return stoi(s, 0, 65535);
}
s16 getS16(std::string name)
{
return stoi(get(name), -32768, 32767);
}
s32 getS32(std::string name)
{
return stoi(get(name));
}
private:
core::map<std::string, std::string> m_settings;
};
#endif #endif