/* Copyright (c) 2019 yvt This file is part of OpenSpades. OpenSpades 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 3 of the License, or (at your option) any later version. OpenSpades 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 OpenSpades. If not, see . */ #include #include "GameMap.h" #include "GameMapLoader.h" #include #include #include #include #include #include namespace spades { namespace client { struct GameMapLoader::Result { // The following fields are mutually exclusive. std::exception_ptr exceptionThrown; Handle gameMap; }; struct GameMapLoader::Decoder : public IRunnable { GameMapLoader &parent; StreamHandle rawDataReader; Decoder(GameMapLoader &parent, StreamHandle rawDataReader) : parent{parent}, rawDataReader{rawDataReader} {} void Run() override { SPADES_MARK_FUNCTION(); std::unique_ptr result{new Result()}; try { DeflateStream inflate(&*rawDataReader, CompressModeDecompress, false); GameMap *gameMapPtr = GameMap::Load(&inflate, [this](int x) { HandleProgress(x); }); result->gameMap = Handle{gameMapPtr, false}; } catch (...) { // Capture the current exception result->exceptionThrown = std::current_exception(); } // Send back the result parent.resultCell.store(std::move(result)); } void HandleProgress(int numColumnsLoaded) { parent.progressCell.store(numColumnsLoaded); } }; GameMapLoader::GameMapLoader() : progressCell{0} { SPADES_MARK_FUNCTION(); auto pipe = CreatePipeStream(); rawDataWriter = StreamHandle{std::get<0>(pipe)}; auto rawDataReader = StreamHandle{std::get<1>(pipe)}; decodingThreadRunnable.reset(new Decoder(*this, rawDataReader)); // Drop `rawDataReader` before the thread starts. `StreamHandle`'s internally // ref-counted and it's not thread-safe. So, if we don't drop it here, there'll be // a data race between the constructor of `Decoder` and the deconstruction of the local // variable `rawDataReader`. rawDataReader = StreamHandle{}; decodingThread.reset(new Thread(&*decodingThreadRunnable)); decodingThread->Start(); } GameMapLoader::~GameMapLoader() { SPADES_MARK_FUNCTION(); // Hang up the writer. This causes the decoder thread to exit gracefully. rawDataWriter = StreamHandle{}; decodingThread->Join(); decodingThread.reset(); } void GameMapLoader::AddRawChunk(const char *bytes, std::size_t numBytes) { SPADES_MARK_FUNCTION(); if (!rawDataWriter) { SPRaise("The raw data channel is already closed."); } rawDataWriter->Write(bytes, numBytes); } void GameMapLoader::MarkEOF() { SPADES_MARK_FUNCTION(); if (!rawDataWriter) { SPRaise("The raw data channel is already closed."); } rawDataWriter = StreamHandle{}; } bool GameMapLoader::IsComplete() { return resultCell.operator bool(); } void GameMapLoader::WaitComplete() { SPADES_MARK_FUNCTION(); decodingThread->Join(); SPAssert(IsComplete()); } float GameMapLoader::GetProgress() { return static_cast(progressCell.load(std::memory_order_relaxed)) / (512 * 512); } GameMap *GameMapLoader::TakeGameMap() { SPADES_MARK_FUNCTION(); SPAssert(IsComplete()); std::unique_ptr result = resultCell.take(); SPAssert(result); if (result->gameMap) { return result->gameMap.Unmanage(); } else { std::rethrow_exception(result->exceptionThrown); } } } // namespace client } // namespace spades