//////////////////////////////////////////////////////////////////////////////// // Scorched3D (c) 2000-2004 // // This file is part of Scorched3D. // // Scorched3D 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. // // Scorched3D 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 Scorched3D; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include ServerFileServer *ServerFileServer::instance_ = 0; ServerFileServer *ServerFileServer::instance() { if (!instance_) { instance_ = new ServerFileServer; } return instance_; } ServerFileServer::ServerFileServer() : lastTime_(0), bytesSent_(0) { } ServerFileServer::~ServerFileServer() { } void ServerFileServer::simulate(float timeDifference) { int downloadCount = 0; // TODO this should really be done on a per destination id basis // not on a per tank basis!! // Check how many people are currently downloading // also check for any that have finished downloading std::map tanks = ScorchedServer::instance()->getTankContainer().getPlayingTanks(); std::map::iterator itor; for (itor = tanks.begin(); itor != tanks.end(); itor++) { Tank *tank = (*itor).second; if (tank->getState().getLoading()) { // Does this tank have any more files to send if (!tank->getMod().getFiles().empty()) { // Yes, 1 more tank to send to downloadCount++; } else { // Check if this tank had had a chance to get its mod files if (tank->getMod().getInit()) { // Set this tank to finished tank->getState().setLoading(false); // If all tanks at this destination have finished send init message bool allComplete = true; std::map::iterator seconditor; for (seconditor = tanks.begin(); seconditor != tanks.end(); seconditor++) { Tank *secondtank = (*seconditor).second; if (secondtank->getState().getLoading()) { allComplete = false; break; } } // If this tank is not initialized make it initialized if (tank->getState().getInitializing() && allComplete) { // Tell this destination to start initializing ComsInitializeMessage initMessage; ComsMessageSender::sendToSingleClient(initMessage, tank->getDestinationId()); } } } } } // Do we allow file downloads if (ScorchedServer::instance()->getOptionsGame().getModDownloadSpeed() == 0) { return; } // If no people are downloading then there is nothing to do if (downloadCount == 0) return; // Cacluate the maximum amount that can be downloaded by each // client per second int maxDownloadPerClient = ScorchedServer::instance()->getOptionsGame().getModDownloadSpeed() / downloadCount; // Check if this is the next second unsigned int theTime = time(0); if (theTime > lastTime_) { // If so reset the amount that can be sent by this server lastTime_ = theTime; bytesSent_ = 0; // Reset the sent state for each client for (itor = tanks.begin(); itor != tanks.end(); itor++) { Tank *tank = (*itor).second; if (tank->getState().getLoading()) { tank->getMod().setSent(false); } } } // Send bytes to each ready client for (itor = tanks.begin(); itor != tanks.end(); itor++) { // Make sure we have not sent too much this second already if (bytesSent_ + maxDownloadPerClient > (unsigned int) ScorchedServer::instance()->getOptionsGame().getModDownloadSpeed() + 5) { return; } Tank *tank = (*itor).second; if (tank->getState().getLoading()) { // Check if the client is ready to recieve more // bytes and there is some to send and // we have not sent to this client this second if (tank->getMod().getReadyToReceive() && !tank->getMod().getFiles().empty() && !tank->getMod().getSent()) { // Send bytes to this tank tank->getMod().setSent(true); tank->getMod().setReadyToReceive(false); bytesSent_ += maxDownloadPerClient; sendBytes(tank, maxDownloadPerClient); } } } } void ServerFileServer::sendBytes(Tank *tank, unsigned int size) { ComsFileMessage message; // Fill up the message with files, until it is full while (size > 0) { unsigned int bytesSent = 0; if (!sendNextFile(message, tank, size, bytesSent)) break; size -= bytesSent; } // Add any empty file name to signal end of files for this message message.fileBuffer.addToBuffer(""); // Send the message to the client ComsMessageSender::sendToSingleClient(message, tank->getDestinationId()); } bool ServerFileServer::sendNextFile(ComsFileMessage &message, Tank *tank, unsigned int size, unsigned int &bytesSent) { // Get the next file to send std::list &files = tank->getMod().getFiles(); if (files.empty()) return false; ModIdentifierEntry &entry = files.front(); // Find the next file in the modfiles std::map &modfiles = ScorchedServer::instance()->getModFiles().getFiles(); std::map::iterator findItor = modfiles.find(entry.fileName); DIALOG_ASSERT(findItor != modfiles.end()); ModFileEntry *modentry = (*findItor).second; // Check how much still needs to be sent unsigned int sizeSent = entry.length; unsigned int sizeLeftToSend = modentry->getCompressedSize() - sizeSent; unsigned int sizeToSend = MIN(sizeLeftToSend, size); unsigned int bytesLeft = tank->getMod().getTotalLeft(); bool firstChunk = (sizeSent == 0); bool lastChunk = (sizeToSend == sizeLeftToSend); // Add the bytes to the buffer message.fileBuffer.addToBuffer(modentry->getFileName()); message.fileBuffer.addToBuffer(firstChunk); message.fileBuffer.addToBuffer(lastChunk); message.fileBuffer.addToBuffer(bytesLeft); message.fileBuffer.addToBuffer(modentry->getCompressedSize()); message.fileBuffer.addToBuffer(modentry->getUncompressedSize()); message.fileBuffer.addToBuffer(modentry->getCompressedCrc()); message.fileBuffer.addToBuffer(sizeToSend); message.fileBuffer.addDataToBuffer(modentry->getCompressedBytes() + entry.length, sizeToSend); // Update how much we have sent entry.length += sizeToSend; tank->getMod().setTotalLeft(tank->getMod().getTotalLeft() - sizeToSend); // Have we sent the whole file if (sizeToSend == sizeLeftToSend) { // If so remove the file from the list that // still needs to be sent tank->getMod().rmFile(modentry->getFileName()); } bytesSent = sizeToSend; return true; }