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.
+MeshUpdateQueue::(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
#include "main.h"
#include <sstream>
#include "porting.h"
+#include "mapsector.h"
+#include "mapblock_mesh.h"
+#include "mapblock.h"
+
+/*
+ QueuedMeshUpdate
+*/
+
+QueuedMeshUpdate::QueuedMeshUpdate():
+ p(-1337,-1337,-1337),
+ data(NULL),
+ ack_block_to_server(false)
+{
+}
+
+QueuedMeshUpdate::~QueuedMeshUpdate()
+{
+ if(data)
+ delete data;
+}
+
+/*
+ MeshUpdateQueue
+*/
+
+MeshUpdateQueue::MeshUpdateQueue()
+{
+ m_mutex.Init();
+}
+
+MeshUpdateQueue::~MeshUpdateQueue()
+{
+ JMutexAutoLock lock(m_mutex);
+
+ core::list<QueuedMeshUpdate*>::Iterator i;
+ for(i=m_queue.begin(); i!=m_queue.end(); i++)
+ {
+ QueuedMeshUpdate *q = *i;
+ delete q;
+ }
+}
+
+/*
+ peer_id=0 adds with nobody to send to
+*/
+void MeshUpdateQueue::addBlock(v3s16 p, MeshMakeData *data, bool ack_block_to_server)
+{
+ DSTACK(__FUNCTION_NAME);
+
+ assert(data);
+
+ JMutexAutoLock lock(m_mutex);
+
+ /*
+ Find if block is already in queue.
+ If it is, update the data and quit.
+ */
+ core::list<QueuedMeshUpdate*>::Iterator i;
+ for(i=m_queue.begin(); i!=m_queue.end(); i++)
+ {
+ QueuedMeshUpdate *q = *i;
+ if(q->p == p)
+ {
+ if(q->data)
+ delete q->data;
+ q->data = data;
+ if(ack_block_to_server)
+ q->ack_block_to_server = true;
+ return;
+ }
+ }
+
+ /*
+ Add the block
+ */
+ QueuedMeshUpdate *q = new QueuedMeshUpdate;
+ q->p = p;
+ q->data = data;
+ q->ack_block_to_server = ack_block_to_server;
+ m_queue.push_back(q);
+}
+
+// Returned pointer must be deleted
+// Returns NULL if queue is empty
+QueuedMeshUpdate * MeshUpdateQueue::pop()
+{
+ JMutexAutoLock lock(m_mutex);
+
+ core::list<QueuedMeshUpdate*>::Iterator i = m_queue.begin();
+ if(i == m_queue.end())
+ return NULL;
+ QueuedMeshUpdate *q = *i;
+ m_queue.erase(i);
+ return q;
+}
+
+/*
+ MeshUpdateThread
+*/
void * MeshUpdateThread::Thread()
{
while(getRun())
{
+ /*// Wait for output queue to flush.
+ // Allow 2 in queue, this makes less frametime jitter.
+ // Umm actually, there is no much difference
+ if(m_queue_out.size() >= 2)
+ {
+ sleep_ms(3);
+ continue;
+ }*/
+
QueuedMeshUpdate *q = m_queue_in.pop();
if(q == NULL)
{
- sleep_ms(50);
+ sleep_ms(3);
continue;
}
+ ScopeProfiler sp(&g_profiler, "mesh make");
+
scene::SMesh *mesh_new = NULL;
mesh_new = makeMapBlockMesh(q->data);
Client::Client(
IrrlichtDevice *device,
const char *playername,
+ std::string password,
MapDrawControl &control):
m_mesh_update_thread(),
m_env(
m_server_ser_ver(SER_FMT_VER_INVALID),
m_inventory_updated(false),
m_time_of_day(0),
- m_map_seed(0)
+ m_map_seed(0),
+ m_password(password),
+ m_access_denied(false)
{
m_packetcounter_timer = 0.0;
- m_delete_unused_sectors_timer = 0.0;
+ //m_delete_unused_sectors_timer = 0.0;
m_connection_reinit_timer = 0.0;
m_avg_rtt_timer = 0.0;
m_playerpos_send_timer = 0.0;
m_packetcounter.clear();
}
}
+
+ // Get connection status
+ bool connected = connectedAndInitialized();
+#if 0
{
/*
Delete unused sectors
core::list<v3s16> deleted_blocks;
- float delete_unused_sectors_timeout =
- g_settings.getFloat("client_delete_unused_sectors_timeout");
+ g_settings.getFloat("client_unload_unused_data_timeout");
// Delete sector blocks
- /*u32 num = m_env.getMap().deleteUnusedSectors
+ /*u32 num = m_env.getMap().unloadUnusedData
(delete_unused_sectors_timeout,
true, &deleted_blocks);*/
// Delete whole sectors
- u32 num = m_env.getMap().deleteUnusedSectors
+ m_env.getMap().unloadUnusedData
(delete_unused_sectors_timeout,
- false, &deleted_blocks);
+ &deleted_blocks);
- if(num > 0)
+ if(deleted_blocks.size() > 0)
{
/*dstream<<DTIME<<"Client: Deleted blocks of "<<num
<<" unused sectors"<<std::endl;*/
- dstream<<DTIME<<"Client: Deleted "<<num
- <<" unused sectors"<<std::endl;
+ /*dstream<<DTIME<<"Client: Deleted "<<num
+ <<" unused sectors"<<std::endl;*/
/*
Send info to server
}
}
}
-
- bool connected = connectedAndInitialized();
+#endif
if(connected == false)
{
// [0] u16 TOSERVER_INIT
// [2] u8 SER_FMT_VER_HIGHEST
// [3] u8[20] player_name
- SharedBuffer<u8> data(2+1+PLAYERNAME_SIZE);
+ // [23] u8[28] password
+ 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
Send(0, data, false);
}
Do stuff if connected
*/
+ /*
+ Run Map's timers and unload unused data
+ */
+ const float map_timer_and_unload_dtime = 5.25;
+ if(m_map_timer_and_unload_interval.step(dtime, map_timer_and_unload_dtime))
+ {
+ ScopeProfiler sp(&g_profiler, "Client: map timer and unload");
+ core::list<v3s16> deleted_blocks;
+ m_env.getMap().timerUpdate(map_timer_and_unload_dtime,
+ g_settings.getFloat("client_unload_unused_data_timeout"),
+ &deleted_blocks);
+
+ /*if(deleted_blocks.size() > 0)
+ dstream<<"Client: Unloaded "<<deleted_blocks.size()
+ <<" unused blocks"<<std::endl;*/
+
+ /*
+ Send info to server
+ NOTE: This loop is intentionally iterated the way it is.
+ */
+
+ core::list<v3s16>::Iterator i = deleted_blocks.begin();
+ core::list<v3s16> sendlist;
+ for(;;)
+ {
+ if(sendlist.size() == 255 || i == deleted_blocks.end())
+ {
+ if(sendlist.size() == 0)
+ break;
+ /*
+ [0] u16 command
+ [2] u8 count
+ [3] v3s16 pos_0
+ [3+6] v3s16 pos_1
+ ...
+ */
+ u32 replysize = 2+1+6*sendlist.size();
+ SharedBuffer<u8> reply(replysize);
+ writeU16(&reply[0], TOSERVER_DELETEDBLOCKS);
+ reply[2] = sendlist.size();
+ u32 k = 0;
+ for(core::list<v3s16>::Iterator
+ j = sendlist.begin();
+ j != sendlist.end(); j++)
+ {
+ writeV3S16(&reply[2+1+6*k], *j);
+ k++;
+ }
+ m_con.Send(PEER_ID_SERVER, 1, reply, true);
+
+ if(i == deleted_blocks.end())
+ break;
+
+ sendlist.clear();
+ }
+
+ sendlist.push_back(*i);
+ i++;
+ }
+ }
+
/*
Handle environment
*/
//TimeTaker envtimer("env step", m_device);
// Step environment
m_env.step(dtime);
-
- // Step active blocks
+
+ /*
+ Handle active blocks
+ NOTE: These old objects are DEPRECATED. TODO: Remove
+ */
for(core::map<v3s16, bool>::Iterator
i = m_active_blocks.getIterator();
i.atEnd() == false; i++)
{
v3s16 p = i.getNode()->getKey();
- MapBlock *block = NULL;
- try
- {
- block = m_env.getMap().getBlockNoCreate(p);
- block->stepObjects(dtime, false, m_env.getDayNightRatio());
- }
- catch(InvalidPositionException &e)
- {
- }
+ MapBlock *block = m_env.getMap().getBlockNoCreateNoEx(p);
+ if(block == NULL)
+ continue;
+
+ // Step MapBlockObjects
+ block->stepObjects(dtime, false, m_env.getDayNightRatio());
}
/*
//dstream<<"Client received command="<<(int)command<<std::endl;
- // Execute fast commands straight away
-
if(command == TOCLIENT_INIT)
{
if(datasize < 3)
return;
}
-
+
+ if(command == TOCLIENT_ACCESS_DENIED)
+ {
+ // The server didn't like our password. Note, this needs
+ // to be processed even if the serialisation format has
+ // not been agreed yet, the same as TOCLIENT_INIT.
+ m_access_denied = true;
+ m_access_denied_reason = L"Unknown";
+ if(datasize >= 4)
+ {
+ std::string datastring((char*)&data[2], datasize-2);
+ std::istringstream is(datastring, std::ios_base::binary);
+ m_access_denied_reason = deSerializeWideString(is);
+ }
+ return;
+ }
+
if(ser_version == SER_FMT_VER_INVALID)
{
dout_client<<DTIME<<"WARNING: Client: Server serialization"
MapSector *sector;
MapBlock *block;
- { //envlock
- //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
-
- v2s16 p2d(p.X, p.Z);
- sector = m_env.getMap().emergeSector(p2d);
-
- v2s16 sp = sector->getPos();
- if(sp != p2d)
- {
- dstream<<"ERROR: Got sector with getPos()="
- <<"("<<sp.X<<","<<sp.Y<<"), tried to get"
- <<"("<<p2d.X<<","<<p2d.Y<<")"<<std::endl;
- }
-
- assert(sp == p2d);
- //assert(sector->getPos() == p2d);
+ v2s16 p2d(p.X, p.Z);
+ sector = m_env.getMap().emergeSector(p2d);
+
+ assert(sector->getPos() == p2d);
- //TimeTaker timer("MapBlock deSerialize");
- // 0ms
-
- try{
- block = sector->getBlockNoCreate(p.Y);
- /*
- Update an existing block
- */
- //dstream<<"Updating"<<std::endl;
- block->deSerialize(istr, ser_version);
- //block->setChangedFlag();
- }
- catch(InvalidPositionException &e)
- {
- /*
- Create a new block
- */
- //dstream<<"Creating new"<<std::endl;
- block = new MapBlock(&m_env.getMap(), p);
- block->deSerialize(istr, ser_version);
- sector->insertBlock(block);
- //block->setChangedFlag();
-
- //DEBUG
- /*NodeMod mod;
- mod.type = NODEMOD_CHANGECONTENT;
- mod.param = CONTENT_MESE;
- block->setTempMod(v3s16(8,10,8), mod);
- block->setTempMod(v3s16(8,9,8), mod);
- block->setTempMod(v3s16(8,8,8), mod);
- block->setTempMod(v3s16(8,7,8), mod);
- block->setTempMod(v3s16(8,6,8), mod);*/
-#if 0
- /*
- Add some coulds
- Well, this is a dumb way to do it, they should just
- be drawn as separate objects. But the looks of them
- can be tested this way.
- */
- if(p.Y == 3)
- {
- NodeMod mod;
- mod.type = NODEMOD_CHANGECONTENT;
- mod.param = CONTENT_CLOUD;
- v3s16 p2;
- p2.Y = 8;
- for(p2.X=3; p2.X<=13; p2.X++)
- for(p2.Z=3; p2.Z<=13; p2.Z++)
- {
- block->setTempMod(p2, mod);
- }
- }
-#endif
- }
- } //envlock
+ //TimeTaker timer("MapBlock deSerialize");
+ // 0ms
+
+ block = sector->getBlockNoCreateNoEx(p.Y);
+ if(block)
+ {
+ /*
+ Update an existing block
+ */
+ //dstream<<"Updating"<<std::endl;
+ block->deSerialize(istr, ser_version);
+ }
+ else
+ {
+ /*
+ Create a new block
+ */
+ //dstream<<"Creating new"<<std::endl;
+ block = new MapBlock(&m_env.getMap(), p);
+ block->deSerialize(istr, ser_version);
+ sector->insertBlock(block);
+
+ //DEBUG
+ /*NodeMod mod;
+ mod.type = NODEMOD_CHANGECONTENT;
+ mod.param = CONTENT_MESE;
+ block->setTempMod(v3s16(8,10,8), mod);
+ block->setTempMod(v3s16(8,9,8), mod);
+ block->setTempMod(v3s16(8,8,8), mod);
+ block->setTempMod(v3s16(8,7,8), mod);
+ block->setTempMod(v3s16(8,6,8), mod);*/
+ }
#if 0
/*
//m_env.getClientMap().updateMeshes(block->getPos(), getDayNightRatio());
+ /*
+ Add it to mesh update queue and set it to be acknowledged after update.
+ */
+ //std::cerr<<"Adding mesh update task for received block"<<std::endl;
addUpdateMeshTaskWithEdge(p, true);
}
else if(command == TOCLIENT_PLAYERPOS)
}
else if(command == TOCLIENT_SECTORMETA)
{
+ dstream<<"Client received DEPRECATED TOCLIENT_SECTORMETA"<<std::endl;
+#if 0
/*
[0] u16 command
[2] u8 sector count
((ClientMap&)m_env.getMap()).deSerializeSector(pos, is);
}
} //envlock
+#endif
}
else if(command == TOCLIENT_INVENTORY)
{
/*
Read block objects
+ NOTE: Deprecated stuff here, TODO: Remove
*/
// Read active block count
if(datasize < 4)
return;
- u16 time = readU16(&data[2]);
- time = time % 24000;
- m_time_of_day = time;
- //dstream<<"Client: time="<<time<<std::endl;
+ u16 time_of_day = readU16(&data[2]);
+ time_of_day = time_of_day % 24000;
+ //dstream<<"Client: time_of_day="<<time_of_day<<std::endl;
/*
- Day/night
-
time_of_day:
0 = midnight
12000 = midday
*/
{
- u32 dr = time_to_daynight_ratio(m_time_of_day);
+ m_env.setTimeOfDay(time_of_day);
- dstream<<"Client: time_of_day="<<m_time_of_day
+ u32 dr = m_env.getDayNightRatio();
+
+ dstream<<"Client: time_of_day="<<time_of_day
<<", dr="<<dr
<<std::endl;
-
- if(dr != m_env.getDayNightRatio())
- {
- dout_client<<DTIME<<"Client: changing day-night ratio"<<std::endl;
- m_env.setDayNightRatio(dr);
- m_env.expireMeshes(true);
- }
}
}
Send(0, data, true);
}
+void Client::sendChangePassword(const std::wstring oldpassword,
+ const std::wstring newpassword)
+{
+ Player *player = m_env.getLocalPlayer();
+ if(player == NULL)
+ return;
+
+ std::string playername = player->getName();
+ std::string oldpwd = translatePassword(playername, oldpassword);
+ std::string newpwd = translatePassword(playername, newpassword);
+
+ std::ostringstream os(std::ios_base::binary);
+ u8 buf[2+PASSWORD_SIZE*2];
+ /*
+ [0] u16 TOSERVER_PASSWORD
+ [2] u8[28] old password
+ [30] u8[28] new password
+ */
+
+ writeU16(buf, TOSERVER_PASSWORD);
+ for(u32 i=0;i<PASSWORD_SIZE-1;i++)
+ {
+ buf[2+i] = i<oldpwd.length()?oldpwd[i]:0;
+ buf[30+i] = i<newpwd.length()?newpwd[i]:0;
+ }
+ buf[2+PASSWORD_SIZE-1] = 0;
+ buf[30+PASSWORD_SIZE-1] = 0;
+ os.write((char*)buf, 2+PASSWORD_SIZE*2);
+
+ // Make data buffer
+ std::string s = os.str();
+ SharedBuffer<u8> data((u8*)s.c_str(), s.size());
+ // Send as reliable
+ Send(0, data, true);
+}
+
+
void Client::sendDamage(u8 damage)
{
DSTACK(__FUNCTION_NAME);
try
{
- TimeTaker timer3("Client::addNode(): addNodeAndUpdate");
+ //TimeTaker timer3("Client::addNode(): addNodeAndUpdate");
m_env.getMap().addNodeAndUpdate(p, n, modified_blocks);
}
catch(InvalidPositionException &e)
<<std::endl;*/
}
-/*s32 Client::getDayNightIndex()
-{
- assert(m_daynight_i >= 0 && m_daynight_i < DAYNIGHT_CACHE_COUNT);
- return m_daynight_i;
-}*/
-
u32 Client::getDayNightRatio()
{
//JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
return player->hp;
}
+void Client::setTempMod(v3s16 p, NodeMod mod)
+{
+ //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
+ assert(m_env.getMap().mapType() == MAPTYPE_CLIENT);
+
+ core::map<v3s16, MapBlock*> affected_blocks;
+ ((ClientMap&)m_env.getMap()).setTempMod(p, mod,
+ &affected_blocks);
+
+ for(core::map<v3s16, MapBlock*>::Iterator
+ i = affected_blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ i.getNode()->getValue()->updateMesh(m_env.getDayNightRatio());
+ }
+}
+
+void Client::clearTempMod(v3s16 p)
+{
+ //JMutexAutoLock envlock(m_env_mutex); //bulk comment-out
+ assert(m_env.getMap().mapType() == MAPTYPE_CLIENT);
+
+ core::map<v3s16, MapBlock*> affected_blocks;
+ ((ClientMap&)m_env.getMap()).clearTempMod(p,
+ &affected_blocks);
+
+ for(core::map<v3s16, MapBlock*>::Iterator
+ i = affected_blocks.getIterator();
+ i.atEnd() == false; i++)
+ {
+ i.getNode()->getValue()->updateMesh(m_env.getDayNightRatio());
+ }
+}
+
void Client::addUpdateMeshTask(v3s16 p, bool ack_to_server)
{
/*dstream<<"Client::addUpdateMeshTask(): "
MapBlock *b = m_env.getMap().getBlockNoCreateNoEx(p);
if(b == NULL)
return;
-
+
/*
Create a task to update the mesh of the block
*/
{
//TimeTaker timer("data fill");
- // 0ms
+ // Release: ~0ms
+ // Debug: 1-6ms, avg=2ms
data->fill(getDayNightRatio(), b);
}
}
#endif
+ /*
+ Mark mesh as non-expired at this point so that it can already
+ be marked as expired again if the data changes
+ */
b->setMeshExpired(false);
}