diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/auth.cpp | 22 | ||||
-rw-r--r-- | src/auth.h | 2 | ||||
-rw-r--r-- | src/base64.cpp | 7 | ||||
-rw-r--r-- | src/client.cpp | 6 | ||||
-rw-r--r-- | src/defaultsettings.cpp | 3 | ||||
-rw-r--r-- | src/environment.cpp | 52 | ||||
-rw-r--r-- | src/environment.h | 1 | ||||
-rw-r--r-- | src/game.cpp | 19 | ||||
-rw-r--r-- | src/guiPasswordChange.cpp | 28 | ||||
-rw-r--r-- | src/guiPasswordChange.h | 26 | ||||
-rw-r--r-- | src/main.cpp | 34 | ||||
-rw-r--r-- | src/main.h | 4 | ||||
-rw-r--r-- | src/map.cpp | 6 | ||||
-rw-r--r-- | src/map.h | 6 | ||||
-rw-r--r-- | src/mapblock.cpp | 4 | ||||
-rw-r--r-- | src/nodemetadata.cpp | 155 | ||||
-rw-r--r-- | src/profiler.h | 131 | ||||
-rw-r--r-- | src/server.cpp | 215 | ||||
-rw-r--r-- | src/server.h | 15 | ||||
-rw-r--r-- | src/servercommand.cpp | 29 | ||||
-rw-r--r-- | src/servercommand.h | 28 | ||||
-rw-r--r-- | src/servermain.cpp | 3 | ||||
-rw-r--r-- | src/utility.cpp | 8 | ||||
-rw-r--r-- | src/utility.h | 3 |
24 files changed, 606 insertions, 201 deletions
diff --git a/src/auth.cpp b/src/auth.cpp index 49985e697..5d61243c6 100644 --- a/src/auth.cpp +++ b/src/auth.cpp @@ -77,7 +77,8 @@ u64 stringToPrivs(std::string str) } AuthManager::AuthManager(const std::string &authfilepath): - m_authfilepath(authfilepath) + m_authfilepath(authfilepath), + m_modified(false) { m_mutex.Init(); @@ -138,6 +139,8 @@ void AuthManager::load() ad.privs = privs; m_authdata[name] = ad; } + + m_modified = false; } void AuthManager::save() @@ -162,6 +165,8 @@ void AuthManager::save() AuthData ad = i.getNode()->getValue(); os<<name<<":"<<ad.pwd<<":"<<privsToString(ad.privs)<<"\n"; } + + m_modified = false; } bool AuthManager::exists(const std::string &username) @@ -180,6 +185,8 @@ void AuthManager::set(const std::string &username, AuthData ad) JMutexAutoLock lock(m_mutex); m_authdata[username] = ad; + + m_modified = true; } void AuthManager::add(const std::string &username) @@ -187,6 +194,8 @@ void AuthManager::add(const std::string &username) JMutexAutoLock lock(m_mutex); m_authdata[username] = AuthData(); + + m_modified = true; } std::string AuthManager::getPassword(const std::string &username) @@ -214,6 +223,8 @@ void AuthManager::setPassword(const std::string &username, AuthData ad = n->getValue(); ad.pwd = password; n->setValue(ad); + + m_modified = true; } u64 AuthManager::getPrivs(const std::string &username) @@ -240,5 +251,14 @@ void AuthManager::setPrivs(const std::string &username, u64 privs) AuthData ad = n->getValue(); ad.privs = privs; n->setValue(ad); + + m_modified = true; } +bool AuthManager::isModified() +{ + JMutexAutoLock lock(m_mutex); + return m_modified; +} + + diff --git a/src/auth.h b/src/auth.h index 472409d46..62dced2a3 100644 --- a/src/auth.h +++ b/src/auth.h @@ -89,10 +89,12 @@ public: const std::string &password); u64 getPrivs(const std::string &username); void setPrivs(const std::string &username, u64 privs); + bool isModified(); private: JMutex m_mutex; std::string m_authfilepath; core::map<std::string, AuthData> m_authdata; + bool m_modified; }; #endif diff --git a/src/base64.cpp b/src/base64.cpp index 2a863d161..0dfba5013 100644 --- a/src/base64.cpp +++ b/src/base64.cpp @@ -71,9 +71,10 @@ std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_ for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; - - while((i++ < 3)) - ret += '='; + + // Don't pad it with = + /*while((i++ < 3)) + ret += '=';*/ } diff --git a/src/client.cpp b/src/client.cpp index 79bbd8021..e494056f2 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -306,8 +306,14 @@ void Client::step(float dtime) SharedBuffer<u8> data(2+1+PLAYERNAME_SIZE+PASSWORD_SIZE); writeU16(&data[0], TOSERVER_INIT); writeU8(&data[2], SER_FMT_VER_HIGHEST); + memset((char*)&data[3], 0, PLAYERNAME_SIZE); snprintf((char*)&data[3], PLAYERNAME_SIZE, "%s", myplayer->getName()); + + /*dstream<<"Client: password hash is \""<<m_password<<"\"" + <<std::endl;*/ + + memset((char*)&data[23], 0, PASSWORD_SIZE); snprintf((char*)&data[23], PASSWORD_SIZE, "%s", m_password.c_str()); // Send as unreliable diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 6fcdc1dbb..73f22a7f7 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -74,12 +74,13 @@ void set_default_settings() g_settings.setDefault("give_initial_stuff", "false"); g_settings.setDefault("default_password", ""); g_settings.setDefault("default_privs", "build, shout"); + g_settings.setDefault("profiler_print_interval", "0"); g_settings.setDefault("objectdata_interval", "0.2"); g_settings.setDefault("active_object_range", "2"); g_settings.setDefault("max_simultaneous_block_sends_per_client", "1"); //g_settings.setDefault("max_simultaneous_block_sends_per_client", "2"); - g_settings.setDefault("max_simultaneous_block_sends_server_total", "4"); + g_settings.setDefault("max_simultaneous_block_sends_server_total", "8"); g_settings.setDefault("max_block_send_distance", "8"); g_settings.setDefault("max_block_generate_distance", "8"); g_settings.setDefault("time_send_interval", "20"); diff --git a/src/environment.cpp b/src/environment.cpp index 36c754d57..f5f20d0e5 100644 --- a/src/environment.cpp +++ b/src/environment.cpp @@ -729,6 +729,16 @@ void ServerEnvironment::step(float dtime) // Activate stored objects activateObjects(block); + // Run node metadata + bool changed = block->m_node_metadata.step((float)dtime_s); + if(changed) + { + MapEditEvent event; + event.type = MEET_BLOCK_NODE_METADATA_CHANGED; + event.p = p; + m_map->dispatchEvent(&event); + } + // TODO: Do something // TODO: Implement usage of ActiveBlockModifier @@ -762,8 +772,10 @@ void ServerEnvironment::step(float dtime) /* Mess around in active blocks */ - if(m_active_blocks_test_interval.step(dtime, 10.0)) + if(m_active_blocks_nodemetadata_interval.step(dtime, 1.0)) { + float dtime = 1.0; + for(core::map<v3s16, bool>::Iterator i = m_active_blocks.m_list.getIterator(); i.atEnd()==false; i++) @@ -779,7 +791,38 @@ void ServerEnvironment::step(float dtime) // Set current time as timestamp block->setTimestamp(m_game_time); + + // Run node metadata + bool changed = block->m_node_metadata.step(dtime); + if(changed) + { + MapEditEvent event; + event.type = MEET_BLOCK_NODE_METADATA_CHANGED; + event.p = p; + m_map->dispatchEvent(&event); + } + } + } + if(m_active_blocks_test_interval.step(dtime, 10.0)) + { + //float dtime = 10.0; + + for(core::map<v3s16, bool>::Iterator + i = m_active_blocks.m_list.getIterator(); + i.atEnd()==false; i++) + { + v3s16 p = i.getNode()->getKey(); + /*dstream<<"Server: Block ("<<p.X<<","<<p.Y<<","<<p.Z + <<") being handled"<<std::endl;*/ + + MapBlock *block = m_map->getBlockNoCreateNoEx(p); + if(block==NULL) + continue; + + // Set current time as timestamp + block->setTimestamp(m_game_time); + /* Do stuff! @@ -801,8 +844,11 @@ void ServerEnvironment::step(float dtime) { v3s16 p = p0 + block->getPosRelative(); MapNode n = block->getNodeNoEx(p0); - // Test something: - // Convert mud under proper lighting to grass + + /* + Test something: + Convert mud under proper lighting to grass + */ if(n.d == CONTENT_MUD) { if(myrand()%20 == 0) diff --git a/src/environment.h b/src/environment.h index f5cce5933..b4f2a64ca 100644 --- a/src/environment.h +++ b/src/environment.h @@ -246,6 +246,7 @@ private: ActiveBlockList m_active_blocks; IntervalLimiter m_active_blocks_management_interval; IntervalLimiter m_active_blocks_test_interval; + IntervalLimiter m_active_blocks_nodemetadata_interval; // Time from the beginning of the game in seconds. // Incremented in step(). u32 m_game_time; diff --git a/src/game.cpp b/src/game.cpp index 6932b45f1..e8e6cb71f 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -903,6 +903,10 @@ void the_game( bool first_loop_after_window_activation = true; + // TODO: Convert the static interval timers to these + // Interval limiter for profiler + IntervalLimiter m_profiler_interval; + // Time is in milliseconds // NOTE: getRealTime() causes strange problems in wine (imprecision?) // NOTE: So we have to use getTime() and call run()s between them @@ -1090,6 +1094,21 @@ void the_game( } /* + Profiler + */ + float profiler_print_interval = + g_settings.getFloat("profiler_print_interval"); + if(profiler_print_interval != 0) + { + if(m_profiler_interval.step(0.030, profiler_print_interval)) + { + dstream<<"Profiler:"<<std::endl; + g_profiler.print(dstream); + g_profiler.clear(); + } + } + + /* Direct handling of user input */ diff --git a/src/guiPasswordChange.cpp b/src/guiPasswordChange.cpp index 98b11b432..ec1cd029a 100644 --- a/src/guiPasswordChange.cpp +++ b/src/guiPasswordChange.cpp @@ -1,21 +1,19 @@ /* -Minetest-c55 -Copyright (C) 2010-11 celeron55, Perttu Ahola <celeron55@gmail.com> +Part of Minetest-c55 +Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com> Copyright (C) 2011 Ciaran Gultnieks <ciaran@ciarang.com> -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "guiPasswordChange.h" diff --git a/src/guiPasswordChange.h b/src/guiPasswordChange.h index defac3113..1748baade 100644 --- a/src/guiPasswordChange.h +++ b/src/guiPasswordChange.h @@ -1,21 +1,19 @@ /* -Minetest-c55 +Part of Minetest-c55 Copyright (C) 2010-11 celeron55, Perttu Ahola <celeron55@gmail.com> Copyright (C) 2011 Ciaran Gultnieks <ciaran@ciarang.com> -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef GUIPASSWORDCHANGE_HEADER diff --git a/src/main.cpp b/src/main.cpp index 3d342e596..2cde3b302 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -215,6 +215,8 @@ FIXME: Server sometimes goes into some infinite PeerNotFoundException loop FIXME: The new optimized map sending doesn't sometimes send enough blocks
from big caves and such
+* Take player's walking direction into account in GetNextBlocks
+
Environment:
------------
@@ -308,6 +310,9 @@ Making it more portable: Stuff to do before release:
---------------------------
+Fixes to the current release:
+-----------------------------
+
Stuff to do after release:
---------------------------
- Make sure server handles removing grass when a block is placed (etc)
@@ -386,6 +391,9 @@ Settings g_settings; // This is located in defaultsettings.cpp
extern void set_default_settings();
+// Global profiler
+Profiler g_profiler;
+
/*
Random stuff
*/
@@ -436,7 +444,14 @@ std::ostream *derr_client_ptr = &dstream; class TimeGetter
{
public:
- TimeGetter(IrrlichtDevice *device):
+ virtual u32 getTime() = 0;
+};
+
+// A precise irrlicht one
+class IrrlichtTimeGetter: public TimeGetter
+{
+public:
+ IrrlichtTimeGetter(IrrlichtDevice *device):
m_device(device)
{}
u32 getTime()
@@ -448,8 +463,18 @@ public: private:
IrrlichtDevice *m_device;
};
+// Not so precise one which works without irrlicht
+class SimpleTimeGetter: public TimeGetter
+{
+public:
+ u32 getTime()
+ {
+ return porting::getTimeMs();
+ }
+};
// A pointer to a global instance of the time getter
+// TODO: why?
TimeGetter *g_timegetter = NULL;
u32 getTimeMs()
@@ -1208,6 +1233,9 @@ int main(int argc, char *argv[]) {
DSTACK("Dedicated server branch");
+ // Create time getter
+ g_timegetter = new SimpleTimeGetter();
+
// Create server
Server server(map_dir.c_str());
server.start(port);
@@ -1290,7 +1318,7 @@ int main(int argc, char *argv[]) device = device;
// Create time getter
- g_timegetter = new TimeGetter(device);
+ g_timegetter = new IrrlichtTimeGetter(device);
// Create game callback for menus
g_gamecallback = new MainGameCallback(device);
@@ -1497,6 +1525,8 @@ int main(int argc, char *argv[]) g_settings.set("creative_mode", itos(menudata.creative_mode));
g_settings.set("enable_damage", itos(menudata.enable_damage));
+ // NOTE: These are now checked server side; no need to do it
+ // here, so let's not do it here.
/*// Check for valid parameters, restart menu if invalid.
if(playername == "")
{
diff --git a/src/main.h b/src/main.h index fcf150f87..450525c26 100644 --- a/src/main.h +++ b/src/main.h @@ -28,6 +28,10 @@ extern Settings g_settings; #include "tile.h" extern ITextureSource *g_texturesource; +// Global profiler +#include "profiler.h" +extern Profiler g_profiler; + // Debug streams #include <fstream> diff --git a/src/map.cpp b/src/map.cpp index a49de3c46..f9809e9fd 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -5692,9 +5692,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass) p_nodes_min.Y / MAP_BLOCKSIZE - 1, p_nodes_min.Z / MAP_BLOCKSIZE - 1); v3s16 p_blocks_max( - p_nodes_max.X / MAP_BLOCKSIZE + 1, - p_nodes_max.Y / MAP_BLOCKSIZE + 1, - p_nodes_max.Z / MAP_BLOCKSIZE + 1); + p_nodes_max.X / MAP_BLOCKSIZE, + p_nodes_max.Y / MAP_BLOCKSIZE, + p_nodes_max.Z / MAP_BLOCKSIZE); u32 vertex_count = 0; @@ -46,8 +46,14 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MAPTYPE_CLIENT 2 enum MapEditEventType{ + // Node added (changed from air or something else to something) MEET_ADDNODE, + // Node removed (changed to air) MEET_REMOVENODE, + // Node metadata of block changed (not knowing which node exactly) + // p stores block coordinate + MEET_BLOCK_NODE_METADATA_CHANGED, + // Anything else MEET_OTHER }; diff --git a/src/mapblock.cpp b/src/mapblock.cpp index 7adb8c2df..e31596b9c 100644 --- a/src/mapblock.cpp +++ b/src/mapblock.cpp @@ -540,10 +540,10 @@ void updateFastFaceRow( v3s16 p_next; - bool next_makes_face; + bool next_makes_face = false; v3s16 next_p_corrected; v3s16 next_face_dir_corrected; - u8 next_lights[4]; + u8 next_lights[4] = {0,0,0,0}; TileSpec next_tile; // If at last position, there is nothing to compare to and diff --git a/src/nodemetadata.cpp b/src/nodemetadata.cpp index 308a33854..f9468e4fa 100644 --- a/src/nodemetadata.cpp +++ b/src/nodemetadata.cpp @@ -268,91 +268,100 @@ void FurnaceNodeMetadata::inventoryModified() } bool FurnaceNodeMetadata::step(float dtime) { + if(dtime > 60.0) + dstream<<"Furnace stepping a long time ("<<dtime<<")"<<std::endl; // Update at a fixed frequency - const float interval = 0.5; + const float interval = 2.0; m_step_accumulator += dtime; - if(m_step_accumulator < interval) - return false; - m_step_accumulator -= interval; - dtime = interval; - - //dstream<<"Furnace step dtime="<<dtime<<std::endl; - - InventoryList *dst_list = m_inventory->getList("dst"); - assert(dst_list); - - InventoryList *src_list = m_inventory->getList("src"); - assert(src_list); - InventoryItem *src_item = src_list->getItem(0); - - // Start only if there are free slots in dst, so that it can - // accomodate any result item - if(dst_list->getFreeSlots() > 0 && src_item && src_item->isCookable()) - { - m_src_totaltime = 3; - } - else + bool changed = false; + while(m_step_accumulator > interval) { - m_src_time = 0; - m_src_totaltime = 0; - } + m_step_accumulator -= interval; + dtime = interval; - if(m_fuel_time < m_fuel_totaltime) - { - //dstream<<"Furnace is active"<<std::endl; - m_fuel_time += dtime; - m_src_time += dtime; - if(m_src_time >= m_src_totaltime && m_src_totaltime > 0.001 - && src_item) + //dstream<<"Furnace step dtime="<<dtime<<std::endl; + + InventoryList *dst_list = m_inventory->getList("dst"); + assert(dst_list); + + InventoryList *src_list = m_inventory->getList("src"); + assert(src_list); + InventoryItem *src_item = src_list->getItem(0); + + // Start only if there are free slots in dst, so that it can + // accomodate any result item + if(dst_list->getFreeSlots() > 0 && src_item && src_item->isCookable()) + { + m_src_totaltime = 3; + } + else { - InventoryItem *cookresult = src_item->createCookResult(); - dst_list->addItem(cookresult); - src_list->decrementMaterials(1); m_src_time = 0; m_src_totaltime = 0; } - return true; - } - - if(src_item == NULL || m_src_totaltime < 0.001) - { - return false; - } - - bool changed = false; - //dstream<<"Furnace is out of fuel"<<std::endl; + if(m_fuel_time < m_fuel_totaltime) + { + //dstream<<"Furnace is active"<<std::endl; + m_fuel_time += dtime; + m_src_time += dtime; + if(m_src_time >= m_src_totaltime && m_src_totaltime > 0.001 + && src_item) + { + InventoryItem *cookresult = src_item->createCookResult(); + dst_list->addItem(cookresult); + src_list->decrementMaterials(1); + m_src_time = 0; + m_src_totaltime = 0; + } + changed = true; + continue; + } + + if(src_item == NULL || m_src_totaltime < 0.001) + { + continue; + } + + //dstream<<"Furnace is out of fuel"<<std::endl; - InventoryList *fuel_list = m_inventory->getList("fuel"); - assert(fuel_list); - InventoryItem *fuel_item = fuel_list->getItem(0); + InventoryList *fuel_list = m_inventory->getList("fuel"); + assert(fuel_list); + InventoryItem *fuel_item = fuel_list->getItem(0); - if(ItemSpec(ITEM_MATERIAL, CONTENT_TREE).checkItem(fuel_item)) - { - m_fuel_totaltime = 10; - m_fuel_time = 0; - fuel_list->decrementMaterials(1); - changed = true; - } - else if(ItemSpec(ITEM_MATERIAL, CONTENT_WOOD).checkItem(fuel_item)) - { - m_fuel_totaltime = 5; - m_fuel_time = 0; - fuel_list->decrementMaterials(1); - changed = true; - } - else if(ItemSpec(ITEM_CRAFT, "lump_of_coal").checkItem(fuel_item)) - { - m_fuel_totaltime = 10; - m_fuel_time = 0; - fuel_list->decrementMaterials(1); - changed = true; - } - else - { - //dstream<<"No fuel found"<<std::endl; + if(ItemSpec(ITEM_MATERIAL, CONTENT_TREE).checkItem(fuel_item)) + { + m_fuel_totaltime = 30; + m_fuel_time = 0; + fuel_list->decrementMaterials(1); + changed = true; + } + else if(ItemSpec(ITEM_MATERIAL, CONTENT_WOOD).checkItem(fuel_item)) + { + m_fuel_totaltime = 30/4; + m_fuel_time = 0; + fuel_list->decrementMaterials(1); + changed = true; + } + else if(ItemSpec(ITEM_CRAFT, "Stick").checkItem(fuel_item)) + { + m_fuel_totaltime = 30/4/4; + m_fuel_time = 0; + fuel_list->decrementMaterials(1); + changed = true; + } + else if(ItemSpec(ITEM_CRAFT, "lump_of_coal").checkItem(fuel_item)) + { + m_fuel_totaltime = 40; + m_fuel_time = 0; + fuel_list->decrementMaterials(1); + changed = true; + } + else + { + //dstream<<"No fuel found"<<std::endl; + } } - return changed; } diff --git a/src/profiler.h b/src/profiler.h new file mode 100644 index 000000000..a30e34a7c --- /dev/null +++ b/src/profiler.h @@ -0,0 +1,131 @@ +/* +Minetest-c55 +Copyright (C) 2011 celeron55, Perttu Ahola <celeron55@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef PROFILER_HEADER +#define PROFILER_HEADER + +#include "common_irrlicht.h" +#include <string> +#include "utility.h" +#include <jmutex.h> +#include <jmutexautolock.h> + +/* + Time profiler +*/ + +class Profiler +{ +public: + Profiler() + { + m_mutex.Init(); + } + + void add(const std::string &name, u32 duration) + { + JMutexAutoLock lock(m_mutex); + core::map<std::string, u32>::Node *n = m_data.find(name); + if(n == NULL) + { + m_data[name] = duration; + } + else + { + n->setValue(n->getValue()+duration); + } + } + + void clear() + { + JMutexAutoLock lock(m_mutex); + for(core::map<std::string, u32>::Iterator + i = m_data.getIterator(); + i.atEnd() == false; i++) + { + i.getNode()->setValue(0); + } + } + + void print(std::ostream &o) + { + JMutexAutoLock lock(m_mutex); + for(core::map<std::string, u32>::Iterator + i = m_data.getIterator(); + i.atEnd() == false; i++) + { + std::string name = i.getNode()->getKey(); + o<<name<<": "; + s32 clampsize = 40; + s32 space = clampsize-name.size(); + for(s32 j=0; j<space; j++) + { + if(j%2 == 0 && j < space - 1) + o<<"-"; + else + o<<" "; + } + o<<i.getNode()->getValue(); + o<<std::endl; + } + } + +private: + JMutex m_mutex; + core::map<std::string, u32> m_data; +}; + +class ScopeProfiler +{ +public: + ScopeProfiler(Profiler *profiler, const std::string &name): + m_profiler(profiler), + m_name(name), + m_timer(NULL) + { + if(m_profiler) + m_timer = new TimeTaker(m_name.c_str()); + } + // name is copied + ScopeProfiler(Profiler *profiler, const char *name): + m_profiler(profiler), + m_name(name), + m_timer(NULL) + { + if(m_profiler) + m_timer = new TimeTaker(m_name.c_str()); + } + ~ScopeProfiler() + { + if(m_timer) + { + u32 duration = m_timer->stop(true); + if(m_profiler) + m_profiler->add(m_name, duration); + delete m_timer; + } + } +private: + Profiler *m_profiler; + std::string m_name; + TimeTaker *m_timer; +}; + +#endif + diff --git a/src/server.cpp b/src/server.cpp index 56874c46c..96fcc0d07 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -312,11 +312,14 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, TimeTaker timer("RemoteClient::GetNextBlocks", &timer_result);*/ // Increment timers - m_nearest_unsent_reset_timer += dtime; m_nothing_to_send_pause_timer -= dtime; if(m_nothing_to_send_pause_timer >= 0) + { + // Keep this reset + m_nearest_unsent_reset_timer = 0; return; + } // Won't send anything if already sending if(m_blocks_sending.size() >= g_settings.getU16 @@ -326,14 +329,21 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, return; } + //TimeTaker timer("RemoteClient::GetNextBlocks"); + Player *player = server->m_env.getPlayer(peer_id); assert(player != NULL); v3f playerpos = player->getPosition(); v3f playerspeed = player->getSpeed(); + v3f playerspeeddir(0,0,0); + if(playerspeed.getLength() > 1.0*BS) + playerspeeddir = playerspeed / playerspeed.getLength(); + // Predict to next block + v3f playerpos_predicted = playerpos + playerspeeddir*MAP_BLOCKSIZE*BS; - v3s16 center_nodepos = floatToInt(playerpos, BS); + v3s16 center_nodepos = floatToInt(playerpos_predicted, BS); v3s16 center = getNodeBlockPos(center_nodepos); @@ -344,6 +354,9 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, camera_dir.rotateYZBy(player->getPitch()); camera_dir.rotateXZBy(player->getYaw()); + /*dstream<<"camera_dir=("<<camera_dir.X<<","<<camera_dir.Y<<"," + <<camera_dir.Z<<")"<<std::endl;*/ + /* Get the starting value of the block finder radius. */ @@ -356,11 +369,18 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, /*dstream<<"m_nearest_unsent_reset_timer=" <<m_nearest_unsent_reset_timer<<std::endl;*/ - if(m_nearest_unsent_reset_timer > 5.0) + + // This has to be incremented only when the nothing to send pause + // is not active + m_nearest_unsent_reset_timer += dtime; + + // Reset periodically to avoid possible bugs or other mishaps + if(m_nearest_unsent_reset_timer > 10.0) { m_nearest_unsent_reset_timer = 0; m_nearest_unsent_d = 0; - //dstream<<"Resetting m_nearest_unsent_d"<<std::endl; + /*dstream<<"Resetting m_nearest_unsent_d for " + <<server->getPlayerName(peer_id)<<std::endl;*/ } //s16 last_nearest_unsent_d = m_nearest_unsent_d; @@ -402,11 +422,22 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, s16 d_max = g_settings.getS16("max_block_send_distance"); s16 d_max_gen = g_settings.getS16("max_block_generate_distance"); + // Don't loop very much at a time + if(d_max > d_start+1) + d_max = d_start+1; + /*if(d_max_gen > d_start+2) + d_max_gen = d_start+2;*/ + //dstream<<"Starting from "<<d_start<<std::endl; bool sending_something = false; - for(s16 d = d_start; d <= d_max; d++) + bool no_blocks_found_for_sending = true; + + bool queue_is_full = false; + + s16 d; + for(d = d_start; d <= d_max; d++) { //dstream<<"RemoteClient::SendBlocks(): d="<<d<<std::endl; @@ -451,7 +482,10 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, // Don't select too many blocks for sending if(num_blocks_selected >= max_simul_dynamic) - goto queue_full; + { + queue_is_full = true; + goto queue_full_break; + } // Don't send blocks that are currently being transferred if(m_blocks_sending.find(p) != NULL) @@ -513,6 +547,8 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, } #endif + //dstream<<"d="<<d<<std::endl; + /* Don't generate or send if not in sight */ @@ -527,7 +563,9 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, */ { if(m_blocks_sent.find(p) != NULL) + { continue; + } } /* @@ -539,11 +577,14 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, bool block_is_invalid = false; if(block != NULL) { + // Block is dummy if data doesn't exist. + // It means it has been not found from disk and not generated if(block->isDummy()) { surely_not_found_on_disk = true; } - + + // Block is valid if lighting is up-to-date and data exists if(block->isValid() == false) { block_is_invalid = true; @@ -564,8 +605,8 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, If block is not close, don't send it unless it is near ground level. - Block is not near ground level if night-time mesh - doesn't differ from day-time mesh. + Block is near ground level if night-time mesh + differs from day-time mesh. */ if(d > 3) { @@ -586,14 +627,16 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, } /* - Record the lowest d from which a a block has been + Record the lowest d from which a block has been found being not sent and possibly to exist */ - if(new_nearest_unsent_d == -1 || d < new_nearest_unsent_d) + if(no_blocks_found_for_sending) { if(generate == true) new_nearest_unsent_d = d; } + + no_blocks_found_for_sending = false; /* Add inexistent block to emerge queue. @@ -633,20 +676,30 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime, sending_something = true; } } -queue_full: +queue_full_break: - if(new_nearest_unsent_d != -1) + //dstream<<"Stopped at "<<d<<std::endl; + + if(no_blocks_found_for_sending) { - m_nearest_unsent_d = new_nearest_unsent_d; + if(queue_is_full == false) + new_nearest_unsent_d = d; } + if(new_nearest_unsent_d != -1) + m_nearest_unsent_d = new_nearest_unsent_d; + if(sending_something == false) { m_nothing_to_send_counter++; - if(m_nothing_to_send_counter >= 3) + if((s16)m_nothing_to_send_counter >= + g_settings.getS16("max_block_send_distance")) { // Pause time in seconds - m_nothing_to_send_pause_timer = 2.0; + m_nothing_to_send_pause_timer = 1.0; + /*dstream<<"nothing to send to " + <<server->getPlayerName(peer_id) + <<" (d="<<d<<")"<<std::endl;*/ } } else @@ -1130,12 +1183,16 @@ void Server::AsyncRunStep() dtime = m_step_dtime; } - // Send blocks to clients - SendBlocks(dtime); + { + ScopeProfiler sp(&g_profiler, "Server: selecting and sending " + "blocks to clients"); + // Send blocks to clients + SendBlocks(dtime); + } if(dtime < 0.001) return; - + //dstream<<"Server steps "<<dtime<<std::endl; //dstream<<"Server::AsyncRunStep(): dtime="<<dtime<<std::endl; @@ -1196,12 +1253,14 @@ void Server::AsyncRunStep() { // Process connection's timeouts JMutexAutoLock lock2(m_con_mutex); + ScopeProfiler sp(&g_profiler, "Server: connection timeout processing"); m_con.RunTimeouts(dtime); } { // This has to be called so that the client list gets synced // with the peer list of the connection + ScopeProfiler sp(&g_profiler, "Server: peer change handling"); handlePeerChanges(); } @@ -1209,6 +1268,7 @@ void Server::AsyncRunStep() // Step environment // This also runs Map's timers JMutexAutoLock lock(m_env_mutex); + ScopeProfiler sp(&g_profiler, "Server: environment step"); m_env.step(dtime); } @@ -1225,7 +1285,9 @@ void Server::AsyncRunStep() m_liquid_transform_timer -= 1.00; JMutexAutoLock lock(m_env_mutex); - + + ScopeProfiler sp(&g_profiler, "Server: liquid transform"); + core::map<v3s16, MapBlock*> modified_blocks; m_env.getMap().transformLiquids(modified_blocks); #if 0 @@ -1298,10 +1360,11 @@ void Server::AsyncRunStep() */ { //dstream<<"Server: Checking added and deleted active objects"<<std::endl; - JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); - + + ScopeProfiler sp(&g_profiler, "Server: checking added and deleted objects"); + // Radius inside which objects are active s16 radius = 32; @@ -1446,6 +1509,8 @@ void Server::AsyncRunStep() JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); + ScopeProfiler sp(&g_profiler, "Server: sending object messages"); + // Key = object id // Value = data sent by object core::map<u16, core::list<ActiveObjectMessage>* > buffered_messages; @@ -1572,6 +1637,11 @@ void Server::AsyncRunStep() dstream<<"Server: MEET_REMOVENODE"<<std::endl; sendRemoveNode(event->p, event->already_known_by_peer); } + else if(event->type == MEET_BLOCK_NODE_METADATA_CHANGED) + { + dstream<<"Server: MEET_BLOCK_NODE_METADATA_CHANGED"<<std::endl; + setBlockNotSent(event->p); + } else if(event->type == MEET_OTHER) { dstream<<"WARNING: Server: MEET_OTHER not implemented" @@ -1598,6 +1668,9 @@ void Server::AsyncRunStep() { JMutexAutoLock lock1(m_env_mutex); JMutexAutoLock lock2(m_con_mutex); + + ScopeProfiler sp(&g_profiler, "Server: sending mbo positions"); + SendObjectData(counter); counter = 0.0; @@ -1606,15 +1679,20 @@ void Server::AsyncRunStep() /* Step node metadata + TODO: Move to ServerEnvironment and utilize active block stuff */ - { + /*{ //TimeTaker timer("Step node metadata"); JMutexAutoLock envlock(m_env_mutex); JMutexAutoLock conlock(m_con_mutex); - + + ScopeProfiler sp(&g_profiler, "Server: stepping node metadata"); + core::map<v3s16, MapBlock*> changed_blocks; m_env.getMap().nodeMetadataStep(dtime, changed_blocks); + + // Use setBlockNotSent for(core::map<v3s16, MapBlock*>::Iterator i = changed_blocks.getIterator(); @@ -1630,7 +1708,7 @@ void Server::AsyncRunStep() client->SetBlockNotSent(block->getPos()); } } - } + }*/ /* Trigger emergethread (it somehow gets to a non-triggered but @@ -1655,8 +1733,11 @@ void Server::AsyncRunStep() { counter = 0.0; + ScopeProfiler sp(&g_profiler, "Server: saving stuff"); + // Auth stuff - m_authmanager.save(); + if(m_authmanager.isModified()) + m_authmanager.save(); // Map JMutexAutoLock lock(m_env_mutex); @@ -1842,7 +1923,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id) checkpwd = g_settings.get("default_password"); } - if(password != checkpwd) + if(password != checkpwd && checkpwd != "") { derr_server<<DTIME<<"Server: peer_id="<<peer_id <<": supplied invalid password for " @@ -3581,6 +3662,17 @@ void Server::sendAddNode(v3s16 p, MapNode n, u16 ignore_id, } } +void Server::setBlockNotSent(v3s16 p) +{ + for(core::map<u16, RemoteClient*>::Iterator + i = m_clients.getIterator(); + i.atEnd()==false; i++) + { + RemoteClient *client = i.getNode()->getValue(); + client->SetBlockNotSent(p); + } +} + void Server::SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver) { DSTACK(__FUNCTION_NAME); @@ -3646,20 +3738,24 @@ void Server::SendBlocks(float dtime) core::array<PrioritySortedBlockTransfer> queue; s32 total_sending = 0; - - 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()); + ScopeProfiler sp(&g_profiler, "Server: selecting blocks for sending"); - total_sending += client->SendingCount(); - - if(client->serialization_version == SER_FMT_VER_INVALID) - continue; - - client->GetNextBlocks(this, dtime, queue); + 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()); + + total_sending += client->SendingCount(); + + if(client->serialization_version == SER_FMT_VER_INVALID) + continue; + + client->GetNextBlocks(this, dtime, queue); + } } // Sort. @@ -4531,25 +4627,48 @@ void dedicated_server_loop(Server &server, bool &kill) { DSTACK(__FUNCTION_NAME); - std::cout<<DTIME<<std::endl; - std::cout<<"========================"<<std::endl; - std::cout<<"Running dedicated server"<<std::endl; - std::cout<<"========================"<<std::endl; - std::cout<<std::endl; + dstream<<DTIME<<std::endl; + dstream<<"========================"<<std::endl; + dstream<<"Running dedicated server"<<std::endl; + dstream<<"========================"<<std::endl; + dstream<<std::endl; + + IntervalLimiter m_profiler_interval; for(;;) { // This is kind of a hack but can be done like this // because server.step() is very light - sleep_ms(30); + { + ScopeProfiler sp(&g_profiler, "dedicated server sleep"); + sleep_ms(30); + } server.step(0.030); if(server.getShutdownRequested() || kill) { - std::cout<<DTIME<<" dedicated_server_loop(): Quitting."<<std::endl; + dstream<<DTIME<<" dedicated_server_loop(): Quitting."<<std::endl; break; } + /* + Profiler + */ + float profiler_print_interval = + g_settings.getFloat("profiler_print_interval"); + if(profiler_print_interval != 0) + { + if(m_profiler_interval.step(0.030, profiler_print_interval)) + { + dstream<<"Profiler:"<<std::endl; + g_profiler.print(dstream); + g_profiler.clear(); + } + } + + /* + Player info + */ static int counter = 0; counter--; if(counter <= 0) @@ -4562,10 +4681,10 @@ void dedicated_server_loop(Server &server, bool &kill) u32 sum = PIChecksum(list); if(sum != sum_old) { - std::cout<<DTIME<<"Player info:"<<std::endl; + dstream<<DTIME<<"Player info:"<<std::endl; for(i=list.begin(); i!=list.end(); i++) { - i->PrintLine(&std::cout); + i->PrintLine(&dstream); } } sum_old = sum; diff --git a/src/server.h b/src/server.h index 7b73e476c..791ecdec7 100644 --- a/src/server.h +++ b/src/server.h @@ -480,15 +480,17 @@ private: Additionally, if far_players!=NULL, players further away than far_d_nodes are ignored and their peer_ids are added to far_players */ + // Envlock and conlock should be locked when calling these void sendRemoveNode(v3s16 p, u16 ignore_id=0, core::list<u16> *far_players=NULL, float far_d_nodes=100); void sendAddNode(v3s16 p, MapNode n, u16 ignore_id=0, core::list<u16> *far_players=NULL, float far_d_nodes=100); + void setBlockNotSent(v3s16 p); // Environment and Connection must be locked when called void SendBlockNoLock(u16 peer_id, MapBlock *block, u8 ver); - // Sends blocks to clients + // Sends blocks to clients (locks env and con on its own) void SendBlocks(float dtime); /* @@ -500,6 +502,15 @@ private: // When called, connection mutex should be locked RemoteClient* getClient(u16 peer_id); + // When called, environment mutex should be locked + std::string getPlayerName(u16 peer_id) + { + Player *player = m_env.getPlayer(peer_id); + if(player == NULL) + return "[id="+itos(peer_id); + return player->getName(); + } + /* Get a player from memory or creates one. If player is already connected, return NULL @@ -627,6 +638,8 @@ private: */ u16 m_ignore_map_edit_events_peer_id; + Profiler *m_profiler; + friend class EmergeThread; friend class RemoteClient; }; diff --git a/src/servercommand.cpp b/src/servercommand.cpp index e05578b39..333e29084 100644 --- a/src/servercommand.cpp +++ b/src/servercommand.cpp @@ -1,24 +1,21 @@ /* -Minetest-c55 -Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com> +Part of Minetest-c55 +Copyright (C) 2010-11 celeron55, Perttu Ahola <celeron55@gmail.com> Copyright (C) 2011 Ciaran Gultnieks <ciaran@ciarang.com> -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ - #include "servercommand.h" #include "utility.h" diff --git a/src/servercommand.h b/src/servercommand.h index 058fbe65b..9013bc2a6 100644 --- a/src/servercommand.h +++ b/src/servercommand.h @@ -1,21 +1,19 @@ /* -Minetest-c55 -Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com> +Part of Minetest-c55 +Copyright (C) 2010-11 celeron55, Perttu Ahola <celeron55@gmail.com> Copyright (C) 2011 Ciaran Gultnieks <ciaran@ciarang.com> -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef SERVERCOMMAND_HEADER diff --git a/src/servermain.cpp b/src/servermain.cpp index f3b17000c..f83e2ae76 100644 --- a/src/servermain.cpp +++ b/src/servermain.cpp @@ -79,6 +79,9 @@ Settings g_settings; extern void set_default_settings(); +// Global profiler +Profiler g_profiler; + // A dummy thing ITextureSource *g_texturesource = NULL; diff --git a/src/utility.cpp b/src/utility.cpp index 186881c5a..0721100cb 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -229,10 +229,10 @@ std::string translatePassword(std::string playername, std::wstring password) if(password.length() == 0) return ""; - std::string slt=playername + wide_to_narrow(password); - SHA1 *sha1 = new SHA1(); - sha1->addBytes(slt.c_str(), slt.length()); - unsigned char *digest = sha1->getDigest(); + std::string slt = playername + wide_to_narrow(password); + SHA1 sha1; + sha1.addBytes(slt.c_str(), slt.length()); + unsigned char *digest = sha1.getDigest(); std::string pwd = base64_encode(digest, 20); free(digest); return pwd; diff --git a/src/utility.h b/src/utility.h index f18d31278..534aea483 100644 --- a/src/utility.h +++ b/src/utility.h @@ -244,6 +244,9 @@ inline f32 readF1000(std::istream &is) { char buf[2]; is.read(buf, 2); + // TODO: verify if this gets rid of the valgrind warning + //if(is.gcount() != 2) + // return 0; return readF1000((u8*)buf); } |