/** This runs in a separate thread */ static int fpathThreadFunc(void *) { wzMutexLock(fpathMutex); while (!fpathQuit) { if (pathJobs.empty()) { ASSERT(!waitingForResult, "Waiting for a result (id %u) that doesn't exist.", waitingForResultId); wzMutexUnlock(fpathMutex); wzSemaphoreWait(fpathSemaphore); // Go to sleep until needed. wzMutexLock(fpathMutex); continue; } // Copy the first job from the queue. packagedPathJob job = std::move(pathJobs.front()); pathJobs.pop_front(); wzMutexUnlock(fpathMutex); job(); wzMutexLock(fpathMutex); waitingForResult = false; objTrace(waitingForResultId, "These are the droids you are looking for."); wzSemaphorePost(waitingForResultSemaphore); } wzMutexUnlock(fpathMutex); return 0; }
/** This runs in a separate thread */ static int fpathThreadFunc(void *) { wzMutexLock(fpathMutex); while (!fpathQuit) { if (pathJobs.empty()) { ASSERT(!waitingForResult, "Waiting for a result (id %u) that doesn't exist.", waitingForResultId); wzMutexUnlock(fpathMutex); wzSemaphoreWait(fpathSemaphore); // Go to sleep until needed. wzMutexLock(fpathMutex); continue; } // Copy the first job from the queue. Don't pop yet, since the main thread may want to set .deleted = true. PATHJOB job = pathJobs.front(); wzMutexUnlock(fpathMutex); // Execute path-finding for this job using our local temporaries PATHRESULT result; result.droidID = job.droidID; memset(&result.sMove, 0, sizeof(result.sMove)); result.retval = FPR_FAILED; result.originalDest = Vector2i(job.destX, job.destY); // we need to lock BEFORE we fiddle with the data, or we get ugly data race conditions. wzMutexLock(fpathMutex); fpathExecute(&job, &result); ASSERT(pathJobs.front().droidID == job.droidID, "Bug"); // The front of pathJobs may have .deleted set to true, but should not otherwise have been modified or deleted. if (!pathJobs.front().deleted) { pathResults.push_back(result); } pathJobs.pop_front(); // Unblock the main thread, if it was waiting for this particular result. if (waitingForResult && waitingForResultId == job.droidID) { waitingForResult = false; objTrace(waitingForResultId, "These are the droids you are looking for."); wzSemaphorePost(waitingForResultSemaphore); } } wzMutexUnlock(fpathMutex); return 0; }
void fpathRemoveDroidData(int id) { wzMutexLock(fpathMutex); for (std::list<PATHJOB>::iterator psJob = pathJobs.begin(); psJob != pathJobs.end(); ++psJob) { if (psJob->droidID == id) { psJob->deleted = true; // Don't delete the job, since job execution order matters, so tell it to throw away the result after executing, instead. } } for (std::list<PATHRESULT>::iterator psResult = pathResults.begin(); psResult != pathResults.end();) { if (psResult->droidID == id) { psResult = pathResults.erase(psResult); } else { ++psResult; } } wzMutexUnlock(fpathMutex); }
void socketBeginCompression(Socket *sock) { if (sock->isCompressed) { return; // Nothing to do. } wzMutexLock(socketThreadMutex); // Init deflate. sock->zDeflate.zalloc = Z_NULL; sock->zDeflate.zfree = Z_NULL; sock->zDeflate.opaque = Z_NULL; int ret = deflateInit(&sock->zDeflate, 6); ASSERT(ret == Z_OK, "deflateInit failed! Sockets won't work."); sock->zInflate.zalloc = Z_NULL; sock->zInflate.zfree = Z_NULL; sock->zInflate.opaque = Z_NULL; sock->zInflate.avail_in = 0; sock->zInflate.next_in = Z_NULL; ret = inflateInit(&sock->zInflate); ASSERT(ret == Z_OK, "deflateInit failed! Sockets won't work."); sock->zInflateNeedInput = true; sock->isCompressed = true; wzMutexUnlock(socketThreadMutex); }
/** * Similar to write(2) with the exception that this function will block until * <em>all</em> data has been written or an error occurs. * * @return @c size when succesful or @c SOCKET_ERROR if an error occurred. */ ssize_t writeAll(Socket *sock, const void *buf, size_t size, size_t *rawByteCount) { size_t ignored; size_t &rawBytes = rawByteCount != NULL ? *rawByteCount : ignored; rawBytes = 0; if (!sock || sock->fd[SOCK_CONNECTION] == INVALID_SOCKET) { debug(LOG_ERROR, "Invalid socket (EBADF)"); setSockErr(EBADF); return SOCKET_ERROR; } if (sock->writeError) { return SOCKET_ERROR; } if (size > 0) { if (!sock->isCompressed) { wzMutexLock(socketThreadMutex); if (socketThreadWrites.empty()) { wzSemaphorePost(socketThreadSemaphore); } std::vector<uint8_t> &writeQueue = socketThreadWrites[sock]; writeQueue.insert(writeQueue.end(), static_cast<char const *>(buf), static_cast<char const *>(buf) + size); wzMutexUnlock(socketThreadMutex); rawBytes = size; } else { sock->zDeflate.next_in = (Bytef *)buf; sock->zDeflate.avail_in = size; sock->zDeflateInSize += sock->zDeflate.avail_in; do { size_t alreadyHave = sock->zDeflateOutBuf.size(); sock->zDeflateOutBuf.resize(alreadyHave + size + 20); // A bit more than size should be enough to always do everything in one go. sock->zDeflate.next_out = (Bytef *)&sock->zDeflateOutBuf[alreadyHave]; sock->zDeflate.avail_out = sock->zDeflateOutBuf.size() - alreadyHave; int ret = deflate(&sock->zDeflate, Z_NO_FLUSH); ASSERT(ret != Z_STREAM_ERROR, "zlib compression failed!"); // Remove unused part of buffer. sock->zDeflateOutBuf.resize(sock->zDeflateOutBuf.size() - sock->zDeflate.avail_out); } while (sock->zDeflate.avail_out == 0); ASSERT(sock->zDeflate.avail_in == 0, "zlib didn't compress everything!"); } } return size; }
/** Find the length of the result queue, excepting future results. Function is thread-safe. */ static int fpathResultQueueLength(void) { int count = 0; wzMutexLock(fpathMutex); count = pathResults.size(); // O(N) function call for std::list. .empty() is faster, but this function isn't used except in tests. wzMutexUnlock(fpathMutex); return count; }
void socketFlush(Socket *sock, size_t *rawByteCount) { size_t ignored; size_t &rawBytes = rawByteCount != NULL ? *rawByteCount : ignored; rawBytes = 0; if (!sock->isCompressed) { return; // Not compressed, so don't mess with zlib. } // Flush data out of zlib compression state. do { sock->zDeflate.next_in = (Bytef *)NULL; sock->zDeflate.avail_in = 0; size_t alreadyHave = sock->zDeflateOutBuf.size(); sock->zDeflateOutBuf.resize(alreadyHave + 1000); // 100 bytes would probably be enough to flush the rest in one go. sock->zDeflate.next_out = (Bytef *)&sock->zDeflateOutBuf[alreadyHave]; sock->zDeflate.avail_out = sock->zDeflateOutBuf.size() - alreadyHave; int ret = deflate(&sock->zDeflate, Z_PARTIAL_FLUSH); ASSERT(ret != Z_STREAM_ERROR, "zlib compression failed!"); // Remove unused part of buffer. sock->zDeflateOutBuf.resize(sock->zDeflateOutBuf.size() - sock->zDeflate.avail_out); } while (sock->zDeflate.avail_out == 0); if (sock->zDeflateOutBuf.empty()) { return; // No data to flush out. } wzMutexLock(socketThreadMutex); if (socketThreadWrites.empty()) { wzSemaphorePost(socketThreadSemaphore); } std::vector<uint8_t> &writeQueue = socketThreadWrites[sock]; writeQueue.insert(writeQueue.end(), sock->zDeflateOutBuf.begin(), sock->zDeflateOutBuf.end()); wzMutexUnlock(socketThreadMutex); // Primitive network logging, uncomment to use. //printf("Size %3u ->%3zu, buf =", sock->zDeflateInSize, sock->zDeflateOutBuf.size()); //for (unsigned n = 0; n < std::min<unsigned>(sock->zDeflateOutBuf.size(), 40); ++n) printf(" %02X", sock->zDeflateOutBuf[n]); //printf("\n"); // Data sent, don't send again. rawBytes = sock->zDeflateOutBuf.size(); sock->zDeflateInSize = 0; sock->zDeflateOutBuf.clear(); }
static FPATH_RETVAL fpathRoute(MOVE_CONTROL *psMove, unsigned id, int startX, int startY, int tX, int tY, PROPULSION_TYPE propulsionType, DROID_TYPE droidType, FPATH_MOVETYPE moveType, int owner, bool acceptNearest, StructureBounds const &dstStructure) { objTrace(id, "called(*,id=%d,sx=%d,sy=%d,ex=%d,ey=%d,prop=%d,type=%d,move=%d,owner=%d)", id, startX, startY, tX, tY, (int)propulsionType, (int)droidType, (int)moveType, owner); if (!worldOnMap(startX, startY) || !worldOnMap(tX, tY)) { debug(LOG_ERROR, "Droid trying to find path to/from invalid location (%d %d) -> (%d %d).", startX, startY, tX, tY); objTrace(id, "Invalid start/end."); syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = FPR_FAILED", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner); return FPR_FAILED; } // don't have to do anything if already there if (startX == tX && startY == tY) { // return failed to stop them moving anywhere objTrace(id, "Tried to move nowhere"); syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = FPR_FAILED", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner); return FPR_FAILED; } // Check if waiting for a result while (psMove->Status == MOVEWAITROUTE) { objTrace(id, "Checking if we have a path yet"); auto const &I = pathResults.find(id); ASSERT(I != pathResults.end(), "Missing path result promise"); PATHRESULT result = I->second.get(); ASSERT(result.retval != FPR_OK || result.sMove.asPath, "Ok result but no path in list"); // Copy over select fields - preserve others psMove->destination = result.sMove.destination; psMove->numPoints = result.sMove.numPoints; bool correctDestination = tX == result.originalDest.x && tY == result.originalDest.y; psMove->pathIndex = 0; psMove->Status = MOVENAVIGATE; free(psMove->asPath); psMove->asPath = result.sMove.asPath; FPATH_RETVAL retval = result.retval; ASSERT(retval != FPR_OK || psMove->asPath, "Ok result but no path after copy"); ASSERT(retval != FPR_OK || psMove->numPoints > 0, "Ok result but path empty after copy"); // Remove it from the result list pathResults.erase(id); objTrace(id, "Got a path to (%d, %d)! Length=%d Retval=%d", psMove->destination.x, psMove->destination.y, psMove->numPoints, (int)retval); syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = %d, path[%d] = %08X->(%d, %d)", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner, retval, psMove->numPoints, ~crcSumVector2i(0, psMove->asPath, psMove->numPoints), psMove->destination.x, psMove->destination.y); if (!correctDestination) { goto queuePathfinding; // Seems we got the result of an old pathfinding job for this droid, so need to pathfind again. } return retval; } queuePathfinding: // We were not waiting for a result, and found no trivial path, so create new job and start waiting PATHJOB job; job.origX = startX; job.origY = startY; job.droidID = id; job.destX = tX; job.destY = tY; job.dstStructure = dstStructure; job.droidType = droidType; job.propulsion = propulsionType; job.moveType = moveType; job.owner = owner; job.acceptNearest = acceptNearest; job.deleted = false; fpathSetBlockingMap(&job); debug(LOG_NEVER, "starting new job for droid %d 0x%x", id, id); // Clear any results or jobs waiting already. It is a vital assumption that there is only one // job or result for each droid in the system at any time. fpathRemoveDroidData(id); packagedPathJob task([job]() { return fpathExecute(job); }); pathResults[id] = task.get_future(); // Add to end of list wzMutexLock(fpathMutex); bool isFirstJob = pathJobs.empty(); pathJobs.push_back(std::move(task)); wzMutexUnlock(fpathMutex); if (isFirstJob) { wzSemaphorePost(fpathSemaphore); // Wake up processing thread. } objTrace(id, "Queued up a path-finding request to (%d, %d), at least %d items earlier in queue", tX, tY, isFirstJob); syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = FPR_WAIT", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner); return FPR_WAIT; // wait while polling result queue }
static int socketThreadFunction(void *) { wzMutexLock(socketThreadMutex); while (!socketThreadQuit) { #if defined(WZ_OS_UNIX) SOCKET maxfd = INT_MIN; #elif defined(WZ_OS_WIN) SOCKET maxfd = 0; #endif fd_set fds; FD_ZERO(&fds); for (SocketThreadWriteMap::iterator i = socketThreadWrites.begin(); i != socketThreadWrites.end(); ++i) { if (!i->second.empty()) { SOCKET fd = i->first->fd[SOCK_CONNECTION]; maxfd = std::max(maxfd, fd); ASSERT(!FD_ISSET(fd, &fds), "Duplicate file descriptor!"); // Shouldn't be possible, but blocking in send, after select says it won't block, shouldn't be possible either. FD_SET(fd, &fds); } } struct timeval tv = {0, 50 * 1000}; // Check if we can write to any sockets. wzMutexUnlock(socketThreadMutex); int ret = select(maxfd + 1, NULL, &fds, NULL, &tv); wzMutexLock(socketThreadMutex); // We can write to some sockets. (Ignore errors from select, we may have deleted the socket after unlocking the mutex, and before calling select.) if (ret > 0) { for (SocketThreadWriteMap::iterator i = socketThreadWrites.begin(); i != socketThreadWrites.end(); ) { SocketThreadWriteMap::iterator w = i; ++i; Socket *sock = w->first; std::vector<uint8_t> &writeQueue = w->second; ASSERT(!writeQueue.empty(), "writeQueue[sock] must not be empty."); if (!FD_ISSET(sock->fd[SOCK_CONNECTION], &fds)) { continue; // This socket is not ready for writing, or we don't have anything to write. } // Write data. // FIXME SOMEHOW AAARGH This send() call can't block, but unless the socket is not set to blocking (setting the socket to nonblocking had better work, or else), does anyway (at least sometimes, when someone quits). Not reproducible except in public releases. ssize_t ret = send(sock->fd[SOCK_CONNECTION], reinterpret_cast<char *>(&writeQueue[0]), writeQueue.size(), MSG_NOSIGNAL); if (ret != SOCKET_ERROR) { // Erase as much data as written. writeQueue.erase(writeQueue.begin(), writeQueue.begin() + ret); if (writeQueue.empty()) { socketThreadWrites.erase(w); // Nothing left to write, delete from pending list. if (sock->deleteLater) { socketCloseNow(sock); } } } else { switch (getSockErr()) { case EAGAIN: #if defined(EWOULDBLOCK) && EAGAIN != EWOULDBLOCK case EWOULDBLOCK: #endif if (!connectionIsOpen(sock)) { debug(LOG_NET, "Socket error"); sock->writeError = true; socketThreadWrites.erase(w); // Socket broken, don't try writing to it again. if (sock->deleteLater) { socketCloseNow(sock); } break; } case EINTR: break; #if defined(EPIPE) case EPIPE: debug(LOG_NET, "EPIPE generated"); // fall through #endif default: sock->writeError = true; socketThreadWrites.erase(w); // Socket broken, don't try writing to it again. if (sock->deleteLater) { socketCloseNow(sock); } break; } } } } if (socketThreadWrites.empty()) { // Nothing to do, expect to wait. wzMutexUnlock(socketThreadMutex); wzSemaphoreWait(socketThreadSemaphore); wzMutexLock(socketThreadMutex); } } wzMutexUnlock(socketThreadMutex); return 42; // Return value arbitrary and unused. }
static FPATH_RETVAL fpathRoute(MOVE_CONTROL *psMove, int id, int startX, int startY, int tX, int tY, PROPULSION_TYPE propulsionType, DROID_TYPE droidType, FPATH_MOVETYPE moveType, int owner, bool acceptNearest) { objTrace(id, "called(*,id=%d,sx=%d,sy=%d,ex=%d,ey=%d,prop=%d,type=%d,move=%d,owner=%d)", id, startX, startY, tX, tY, (int)propulsionType, (int)droidType, (int)moveType, owner); // don't have to do anything if already there if (startX == tX && startY == tY) { // return failed to stop them moving anywhere objTrace(id, "Tried to move nowhere"); syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = FPR_FAILED", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner); return FPR_FAILED; } // Check if waiting for a result while (psMove->Status == MOVEWAITROUTE) { objTrace(id, "Checking if we have a path yet"); wzMutexLock(fpathMutex); // psNext should be _declared_ here, after the mutex lock! Used to be a race condition, thanks to -Wdeclaration-after-statement style pseudocompiler compatibility. for (std::list<PATHRESULT>::iterator psResult = pathResults.begin(); psResult != pathResults.end(); ++psResult) { if (psResult->droidID != id) { continue; // Wrong result, try next one. } ASSERT(psResult->retval != FPR_OK || psResult->sMove.asPath, "Ok result but no path in list"); // Copy over select fields - preserve others psMove->destination = psResult->sMove.destination; psMove->numPoints = psResult->sMove.numPoints; psMove->Position = 0; psMove->Status = MOVENAVIGATE; free(psMove->asPath); psMove->asPath = psResult->sMove.asPath; FPATH_RETVAL retval = psResult->retval; ASSERT(retval != FPR_OK || psMove->asPath, "Ok result but no path after copy"); ASSERT(retval != FPR_OK || psMove->numPoints > 0, "Ok result but path empty after copy"); // Remove it from the result list pathResults.erase(psResult); wzMutexUnlock(fpathMutex); objTrace(id, "Got a path to (%d, %d)! Length=%d Retval=%d", psMove->destination.x, psMove->destination.y, psMove->numPoints, (int)retval); syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = %d, path[%d] = %08X->(%d, %d)", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner, retval, psMove->numPoints, ~crcSumVector2i(0, psMove->asPath, psMove->numPoints), psMove->destination.x, psMove->destination.y); return retval; } objTrace(id, "No path yet. Waiting."); waitingForResult = true; waitingForResultId = id; wzMutexUnlock(fpathMutex); wzSemaphoreWait(waitingForResultSemaphore); // keep waiting } // We were not waiting for a result, and found no trivial path, so create new job and start waiting PATHJOB job; job.origX = startX; job.origY = startY; job.droidID = id; job.destX = tX; job.destY = tY; job.droidType = droidType; job.propulsion = propulsionType; job.moveType = moveType; job.owner = owner; job.acceptNearest = acceptNearest; job.deleted = false; fpathSetBlockingMap(&job); // Clear any results or jobs waiting already. It is a vital assumption that there is only one // job or result for each droid in the system at any time. fpathRemoveDroidData(id); wzMutexLock(fpathMutex); // Add to end of list bool isFirstJob = pathJobs.empty(); pathJobs.push_back(job); if (isFirstJob) { wzSemaphorePost(fpathSemaphore); // Wake up processing thread. } wzMutexUnlock(fpathMutex); objTrace(id, "Queued up a path-finding request to (%d, %d), at least %d items earlier in queue", tX, tY, isFirstJob); syncDebug("fpathRoute(..., %d, %d, %d, %d, %d, %d, %d, %d, %d) = FPR_WAIT", id, startX, startY, tX, tY, propulsionType, droidType, moveType, owner); return FPR_WAIT; // wait while polling result queue }