From b89026200de08684fb52f4ed8dcea47d8ffee4cc Mon Sep 17 00:00:00 2001 From: Death Killer <884052+deathkiller@users.noreply.github.com> Date: Wed, 1 Nov 2023 22:00:03 +0100 Subject: [PATCH] Multiplayer (part 4), minor changes --- Sources/Jazz2.vcxproj | 5 +- Sources/Jazz2.vcxproj.filters | 3 + Sources/Jazz2/Actors/ActorBase.cpp | 2 +- Sources/Jazz2/Actors/Environment/Spring.cpp | 3 +- Sources/Jazz2/Actors/Explosion.cpp | 12 +- Sources/Jazz2/Actors/Player.cpp | 2 +- Sources/Jazz2/Actors/RemoteActor.cpp | 10 +- Sources/Jazz2/Actors/RemoteActor.h | 1 + Sources/Jazz2/ContentResolver.cpp | 9 +- Sources/Jazz2/ContentResolver.h | 10 +- Sources/Jazz2/Events/EventMap.cpp | 2 +- Sources/Jazz2/ILevelHandler.h | 2 +- Sources/Jazz2/LevelHandler.cpp | 98 +-- Sources/Jazz2/LevelHandler.h | 5 +- .../Jazz2/Multiplayer/ConnectionResult.cpp | 25 + Sources/Jazz2/Multiplayer/ConnectionResult.h | 20 + Sources/Jazz2/Multiplayer/INetworkHandler.h | 6 +- .../Jazz2/Multiplayer/MultiLevelHandler.cpp | 270 ++++++- Sources/Jazz2/Multiplayer/MultiLevelHandler.h | 12 +- Sources/Jazz2/Multiplayer/NetworkManager.cpp | 59 +- Sources/Jazz2/Multiplayer/NetworkManager.h | 5 + Sources/Jazz2/Multiplayer/PacketTypes.h | 1 + Sources/Jazz2/Multiplayer/Reason.h | 24 + Sources/Jazz2/Resources.h | 1 + Sources/Jazz2/UI/Menu/IMenuContainer.h | 6 +- Sources/Jazz2/UI/Menu/InGameMenu.cpp | 4 +- Sources/Jazz2/UI/Menu/InGameMenu.h | 2 +- Sources/Jazz2/UI/Menu/MainMenu.cpp | 4 +- Sources/Jazz2/UI/Menu/MainMenu.h | 2 +- .../Jazz2/UI/Menu/SimpleMessageSection.cpp | 24 +- Sources/Jazz2/UI/Menu/SimpleMessageSection.h | 13 +- Sources/Main.cpp | 75 +- Sources/Shared/Containers/Array.h | 512 +++++++++++--- Sources/Shared/Containers/ArrayView.h | 660 +++++++++++++----- Sources/Shared/Containers/StaticArray.h | 425 ++++++++--- Sources/Shared/Containers/String.cpp | 20 +- Sources/Shared/Containers/String.h | 56 +- Sources/Shared/Containers/StringView.h | 46 +- Sources/Shared/IO/FileSystem.cpp | 2 +- Sources/nCine/Primitives/AABB.h | 47 +- android/app/src/main/cpp/CMakeLists.txt | 2 + cmake/ncine_extra_sources.cmake | 3 + 42 files changed, 1858 insertions(+), 632 deletions(-) create mode 100644 Sources/Jazz2/Multiplayer/ConnectionResult.cpp create mode 100644 Sources/Jazz2/Multiplayer/ConnectionResult.h create mode 100644 Sources/Jazz2/Multiplayer/Reason.h diff --git a/Sources/Jazz2.vcxproj b/Sources/Jazz2.vcxproj index 1b906600..fa08e09e 100644 --- a/Sources/Jazz2.vcxproj +++ b/Sources/Jazz2.vcxproj @@ -1,4 +1,4 @@ - + @@ -344,11 +344,13 @@ + + @@ -709,6 +711,7 @@ + diff --git a/Sources/Jazz2.vcxproj.filters b/Sources/Jazz2.vcxproj.filters index 0f5cba00..e7ba2d21 100644 --- a/Sources/Jazz2.vcxproj.filters +++ b/Sources/Jazz2.vcxproj.filters @@ -1380,6 +1380,9 @@ Header Files\Jazz2\UI\Menu + + Header Files\Jazz2\Multiplayer + diff --git a/Sources/Jazz2/Actors/ActorBase.cpp b/Sources/Jazz2/Actors/ActorBase.cpp index fe72433b..98bd513d 100644 --- a/Sources/Jazz2/Actors/ActorBase.cpp +++ b/Sources/Jazz2/Actors/ActorBase.cpp @@ -429,7 +429,7 @@ namespace Jazz2::Actors auto it = _metadata->Sounds.find(String::nullTerminatedView(identifier)); if (it != _metadata->Sounds.end()) { int idx = (it->second.Buffers.size() > 1 ? Random().Next(0, (int)it->second.Buffers.size()) : 0); - return _levelHandler->PlaySfx(&it->second.Buffers[idx]->Buffer, Vector3f(_pos.X, _pos.Y, 0.0f), false, gain, pitch); + return _levelHandler->PlaySfx(this, identifier, &it->second.Buffers[idx]->Buffer, Vector3f(_pos.X, _pos.Y, 0.0f), false, gain, pitch); } else { return nullptr; } diff --git a/Sources/Jazz2/Actors/Environment/Spring.cpp b/Sources/Jazz2/Actors/Environment/Spring.cpp index 839c1798..16b05d0f 100644 --- a/Sources/Jazz2/Actors/Environment/Spring.cpp +++ b/Sources/Jazz2/Actors/Environment/Spring.cpp @@ -9,8 +9,7 @@ using namespace Jazz2::Tiles; namespace Jazz2::Actors::Environment { Spring::Spring() - : - _cooldown(0.0f) + : _cooldown(0.0f) { } diff --git a/Sources/Jazz2/Actors/Explosion.cpp b/Sources/Jazz2/Actors/Explosion.cpp index 4008c12c..622bc303 100644 --- a/Sources/Jazz2/Actors/Explosion.cpp +++ b/Sources/Jazz2/Actors/Explosion.cpp @@ -6,19 +6,15 @@ namespace Jazz2::Actors { Explosion::Explosion() - : - _lightBrightness(0.0f), - _lightIntensity(0.0f), - _lightRadiusNear(0.0f), - _lightRadiusFar(0.0f) + : _lightBrightness(0.0f), _lightIntensity(0.0f), _lightRadiusNear(0.0f), _lightRadiusFar(0.0f) { } void Explosion::Create(ILevelHandler* levelHandler, const Vector3i& pos, Type type) { std::shared_ptr explosion = std::make_shared(); - uint8_t explosionParams[2]; - *(uint16_t*)&explosionParams[0] = (uint16_t)type; + std::uint8_t explosionParams[2]; + *(std::uint16_t*)&explosionParams[0] = (uint16_t)type; explosion->OnActivated(ActorActivationDetails( levelHandler, pos, @@ -29,7 +25,7 @@ namespace Jazz2::Actors Task Explosion::OnActivatedAsync(const ActorActivationDetails& details) { - _type = (Type)*(uint16_t*)&details.Params[0]; + _type = (Type)*(std::uint16_t*)&details.Params[0]; SetState(ActorState::ForceDisableCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); diff --git a/Sources/Jazz2/Actors/Player.cpp b/Sources/Jazz2/Actors/Player.cpp index 2a260c4e..a11baeee 100644 --- a/Sources/Jazz2/Actors/Player.cpp +++ b/Sources/Jazz2/Actors/Player.cpp @@ -2229,7 +2229,7 @@ namespace Jazz2::Actors auto it = _metadata->Sounds.find(String::nullTerminatedView(identifier)); if (it != _metadata->Sounds.end()) { int idx = (it->second.Buffers.size() > 1 ? Random().Next(0, (int)it->second.Buffers.size()) : 0); - return _levelHandler->PlaySfx(&it->second.Buffers[idx]->Buffer, Vector3f(0.0f, 0.0f, 0.0f), true, gain, pitch); + return _levelHandler->PlaySfx(this, identifier, &it->second.Buffers[idx]->Buffer, Vector3f(0.0f, 0.0f, 0.0f), true, gain, pitch); } else { return nullptr; } diff --git a/Sources/Jazz2/Actors/RemoteActor.cpp b/Sources/Jazz2/Actors/RemoteActor.cpp index 032a2074..46589d17 100644 --- a/Sources/Jazz2/Actors/RemoteActor.cpp +++ b/Sources/Jazz2/Actors/RemoteActor.cpp @@ -23,10 +23,6 @@ namespace Jazz2::Actors _stateBuffer[i].Pos = Vector2f(details.Pos.X, details.Pos.Y); } - async_await RequestMetadataAsync("Interactive/PlayerJazz"_s); - - SetAnimation(AnimState::Idle); - async_return true; } @@ -71,6 +67,12 @@ namespace Jazz2::Actors ActorBase::OnUpdate(timeMult); } + void RemoteActor::AssignMetadata(const StringView& path, AnimState anim) + { + RequestMetadata(path); + SetAnimation(anim); + } + void RemoteActor::SyncWithServer(const Vector2f& pos, AnimState anim, bool isVisible, bool isFacingLeft) { Clock& c = nCine::clock(); diff --git a/Sources/Jazz2/Actors/RemoteActor.h b/Sources/Jazz2/Actors/RemoteActor.h index e47947a7..44ab4a7c 100644 --- a/Sources/Jazz2/Actors/RemoteActor.h +++ b/Sources/Jazz2/Actors/RemoteActor.h @@ -11,6 +11,7 @@ namespace Jazz2::Actors public: RemoteActor(); + void AssignMetadata(const StringView& path, AnimState anim); void SyncWithServer(const Vector2f& pos, AnimState anim, bool isVisible, bool isFacingLeft); protected: diff --git a/Sources/Jazz2/ContentResolver.cpp b/Sources/Jazz2/ContentResolver.cpp index 23dbac20..a3b4aa30 100644 --- a/Sources/Jazz2/ContentResolver.cpp +++ b/Sources/Jazz2/ContentResolver.cpp @@ -56,7 +56,7 @@ namespace Jazz2 } ContentResolver::ContentResolver() - : _isHeadless(false), _isLoading(false), _cachedMetadata(64), _cachedGraphics(128), _palettes{} + : _isHeadless(false), _isLoading(false), _cachedMetadata(64), _cachedGraphics(256), _cachedSounds(192), _palettes{} { InitializePaths(); } @@ -361,8 +361,8 @@ namespace Jazz2 Metadata* ContentResolver::RequestMetadata(const StringView& path) { - auto pathNormalized = fs::ToNativeSeparators(path); - auto it = _cachedMetadata.find(String::nullTerminatedView(pathNormalized)); + String pathNormalized = fs::ToNativeSeparators(path); + auto it = _cachedMetadata.find(pathNormalized); if (it != _cachedMetadata.end()) { // Already loaded - Mark as referenced it->second->Flags |= MetadataFlags::Referenced; @@ -395,6 +395,7 @@ namespace Jazz2 bool multipleAnimsNoStatesWarning = false; std::unique_ptr metadata = std::make_unique(); + metadata->Path = std::move(pathNormalized); metadata->Flags |= MetadataFlags::Referenced; ondemand::parser parser; @@ -555,7 +556,7 @@ namespace Jazz2 } } - return _cachedMetadata.emplace(pathNormalized, std::move(metadata)).first->second.get(); + return _cachedMetadata.emplace(metadata->Path, std::move(metadata)).first->second.get(); } GenericGraphicResource* ContentResolver::RequestGraphics(const StringView& path, uint16_t paletteOffset) diff --git a/Sources/Jazz2/ContentResolver.h b/Sources/Jazz2/ContentResolver.h index 9c52173f..74c8bf2b 100644 --- a/Sources/Jazz2/ContentResolver.h +++ b/Sources/Jazz2/ContentResolver.h @@ -17,6 +17,7 @@ #include "../nCine/Base/HashMap.h" #include +#include #include #include #include @@ -84,6 +85,13 @@ namespace Jazz2 } private: + struct StringRefEqualTo + { + inline bool operator()(const Reference& a, const Reference& b) const noexcept { + return a.get() == b.get(); + } + }; + ContentResolver(); ContentResolver(const ContentResolver&) = delete; @@ -105,7 +113,7 @@ namespace Jazz2 bool _isHeadless; bool _isLoading; uint32_t _palettes[PaletteCount * ColorsPerPalette]; - HashMap> _cachedMetadata; + HashMap, std::unique_ptr, FNV1aHashFunc, StringRefEqualTo> _cachedMetadata; HashMap, std::unique_ptr> _cachedGraphics; HashMap> _cachedSounds; std::unique_ptr _fonts[(int32_t)FontType::Count]; diff --git a/Sources/Jazz2/Events/EventMap.cpp b/Sources/Jazz2/Events/EventMap.cpp index 74b1784a..08e1a751 100644 --- a/Sources/Jazz2/Events/EventMap.cpp +++ b/Sources/Jazz2/Events/EventMap.cpp @@ -169,7 +169,7 @@ namespace Jazz2::Events void EventMap::ActivateEvents(std::int32_t tx1, std::int32_t ty1, std::int32_t tx2, std::int32_t ty2, bool allowAsync) { - auto tiles = _levelHandler->TileMap(); + auto* tiles = _levelHandler->TileMap(); if (tiles == nullptr) { return; } diff --git a/Sources/Jazz2/ILevelHandler.h b/Sources/Jazz2/ILevelHandler.h index 400184cf..c5e901c7 100644 --- a/Sources/Jazz2/ILevelHandler.h +++ b/Sources/Jazz2/ILevelHandler.h @@ -55,7 +55,7 @@ namespace Jazz2 virtual void AddActor(std::shared_ptr actor) = 0; - virtual std::shared_ptr PlaySfx(AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain = 1.0f, float pitch = 1.0f) = 0; + virtual std::shared_ptr PlaySfx(Actors::ActorBase* self, const StringView& identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain = 1.0f, float pitch = 1.0f) = 0; virtual std::shared_ptr PlayCommonSfx(const StringView& identifier, const Vector3f& pos, float gain = 1.0f, float pitch = 1.0f) = 0; virtual void WarpCameraToTarget(const std::shared_ptr& actor, bool fast = false) = 0; virtual bool IsPositionEmpty(Actors::ActorBase* self, const AABBf& aabb, Tiles::TileCollisionParams& params, Actors::ActorBase** collider) = 0; diff --git a/Sources/Jazz2/LevelHandler.cpp b/Sources/Jazz2/LevelHandler.cpp index 49a65ed3..5eaca02e 100644 --- a/Sources/Jazz2/LevelHandler.cpp +++ b/Sources/Jazz2/LevelHandler.cpp @@ -314,46 +314,7 @@ namespace Jazz2 } } - if (/*_difficulty != GameDifficulty::Multiplayer*/true) { - if (!_players.empty()) { - auto& pos = _players[0]->GetPos(); - int32_t tx1 = (int32_t)pos.X / Tiles::TileSet::DefaultTileSize; - int32_t ty1 = (int32_t)pos.Y / Tiles::TileSet::DefaultTileSize; - int32_t tx2 = tx1; - int32_t ty2 = ty1; - - tx1 -= ActivateTileRange; - ty1 -= ActivateTileRange; - tx2 += ActivateTileRange; - ty2 += ActivateTileRange; - - int32_t tx1d = tx1 - 4; - int32_t ty1d = ty1 - 4; - int32_t tx2d = tx2 + 4; - int32_t ty2d = ty2 + 4; - - for (auto& actor : _actors) { - if ((actor->_state & (Actors::ActorState::IsCreatedFromEventMap | Actors::ActorState::IsFromGenerator)) != Actors::ActorState::None) { - Vector2i originTile = actor->_originTile; - if (originTile.X < tx1d || originTile.Y < ty1d || originTile.X > tx2d || originTile.Y > ty2d) { - if (actor->OnTileDeactivated()) { - if ((actor->_state & Actors::ActorState::IsFromGenerator) == Actors::ActorState::IsFromGenerator) { - _eventMap->ResetGenerator(originTile.X, originTile.Y); - } - - _eventMap->Deactivate(originTile.X, originTile.Y); - - actor->_state |= Actors::ActorState::IsDestroyed; - } - } - } - } - - _eventMap->ActivateEvents(tx1, ty1, tx2, ty2, true); - } - - _eventMap->ProcessGenerators(timeMult); - } + ProcessEvents(timeMult); // Weather if (_weatherType != WeatherType::None) { @@ -472,7 +433,7 @@ namespace Jazz2 { float timeMult = theApplication().timeMult(); - if (_pauseMenu == nullptr) { + if (!IsPausable() || _pauseMenu == nullptr) { ResolveCollisions(timeMult); // Ambient Light Transition @@ -702,7 +663,7 @@ namespace Jazz2 _actors.emplace_back(actor); } - std::shared_ptr LevelHandler::PlaySfx(AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) + std::shared_ptr LevelHandler::PlaySfx(Actors::ActorBase* self, const StringView& identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) { auto& player = _playingSounds.emplace_back(std::make_shared(buffer)); player->setPosition(Vector3f(pos.X, pos.Y, 100.0f)); @@ -1279,17 +1240,68 @@ namespace Jazz2 } } + void LevelHandler::BeforeActorDestroyed(Actors::ActorBase* actor) + { + // Nothing to do here + } + + void LevelHandler::ProcessEvents(float timeMult) + { + if (!_players.empty()) { + std::size_t playerCount = _players.size(); + SmallVector playerZones; + playerZones.reserve(playerCount * 2); + for (std::size_t i = 0; i < playerCount; i++) { + auto pos = _players[i]->GetPos(); + std::int32_t tx = (std::int32_t)pos.X / TileSet::DefaultTileSize; + std::int32_t ty = (std::int32_t)pos.Y / TileSet::DefaultTileSize; + + const auto& activationRange = playerZones.emplace_back(tx - ActivateTileRange, ty - ActivateTileRange, tx + ActivateTileRange, ty + ActivateTileRange); + playerZones.emplace_back(activationRange.L - 4, activationRange.T - 4, activationRange.R + 4, activationRange.B + 4); + } + + for (auto& actor : _actors) { + if ((actor->_state & (Actors::ActorState::IsCreatedFromEventMap | Actors::ActorState::IsFromGenerator)) != Actors::ActorState::None) { + Vector2i originTile = actor->_originTile; + bool isInside = false; + for (std::size_t i = 1; i < playerZones.size(); i += 2) { + if (playerZones[i].Contains(originTile)) { + isInside = true; + break; + } + } + + if (!isInside && actor->OnTileDeactivated()) { + if ((actor->_state & Actors::ActorState::IsFromGenerator) == Actors::ActorState::IsFromGenerator) { + _eventMap->ResetGenerator(originTile.X, originTile.Y); + } + + _eventMap->Deactivate(originTile.X, originTile.Y); + actor->_state |= Actors::ActorState::IsDestroyed; + } + } + } + + for (std::size_t i = 0; i < playerZones.size(); i += 2) { + const auto& activationZone = playerZones[i]; + _eventMap->ActivateEvents(activationZone.L, activationZone.T, activationZone.R, activationZone.B, true); + } + } + + _eventMap->ProcessGenerators(timeMult); + } + void LevelHandler::ResolveCollisions(float timeMult) { auto it = _actors.begin(); while (it != _actors.end()) { Actors::ActorBase* actor = it->get(); if (actor->GetState(Actors::ActorState::IsDestroyed)) { + BeforeActorDestroyed(actor); if (actor->CollisionProxyID != Collisions::NullNode) { _collisions.DestroyProxy(actor->CollisionProxyID); actor->CollisionProxyID = Collisions::NullNode; } - it = _actors.erase(it); continue; } diff --git a/Sources/Jazz2/LevelHandler.h b/Sources/Jazz2/LevelHandler.h index 9f5bdd04..9eb32cbd 100644 --- a/Sources/Jazz2/LevelHandler.h +++ b/Sources/Jazz2/LevelHandler.h @@ -110,7 +110,7 @@ namespace Jazz2 void AddActor(std::shared_ptr actor) override; - std::shared_ptr PlaySfx(AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain = 1.0f, float pitch = 1.0f) override; + std::shared_ptr PlaySfx(Actors::ActorBase* self, const StringView& identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) override; std::shared_ptr PlayCommonSfx(const StringView& identifier, const Vector3f& pos, float gain = 1.0f, float pitch = 1.0f) override; void WarpCameraToTarget(const std::shared_ptr& actor, bool fast = false) override; bool IsPositionEmpty(Actors::ActorBase* self, const AABBf& aabb, Tiles::TileCollisionParams& params, Actors::ActorBase** collider) override; @@ -309,6 +309,9 @@ namespace Jazz2 bool _playerFrozenEnabled; uint32_t _lastPressedNumericKey; + virtual void BeforeActorDestroyed(Actors::ActorBase* actor); + virtual void ProcessEvents(float timeMult); + void ResolveCollisions(float timeMult); void InitializeCamera(); void UpdateCamera(float timeMult); diff --git a/Sources/Jazz2/Multiplayer/ConnectionResult.cpp b/Sources/Jazz2/Multiplayer/ConnectionResult.cpp new file mode 100644 index 00000000..b9c6681f --- /dev/null +++ b/Sources/Jazz2/Multiplayer/ConnectionResult.cpp @@ -0,0 +1,25 @@ +#include "ConnectionResult.h" + +#if defined(WITH_MULTIPLAYER) + +#include "../../Common.h" + +namespace Jazz2::Multiplayer +{ + ConnectionResult::ConnectionResult(Reason reason) + : FailureReason(reason) + { + } + + ConnectionResult::ConnectionResult(bool success) + : FailureReason(success ? (Reason)UINT32_MAX : Reason::Unknown) + { + } + + bool ConnectionResult::IsSuccessful() const + { + return FailureReason == (Reason)UINT32_MAX; + } +} + +#endif \ No newline at end of file diff --git a/Sources/Jazz2/Multiplayer/ConnectionResult.h b/Sources/Jazz2/Multiplayer/ConnectionResult.h new file mode 100644 index 00000000..6464f0a2 --- /dev/null +++ b/Sources/Jazz2/Multiplayer/ConnectionResult.h @@ -0,0 +1,20 @@ +#pragma once + +#if defined(WITH_MULTIPLAYER) + +#include "Reason.h" + +namespace Jazz2::Multiplayer +{ + struct ConnectionResult + { + Reason FailureReason; + + ConnectionResult(Reason reason); + ConnectionResult(bool success); + + bool IsSuccessful() const; + }; +} + +#endif \ No newline at end of file diff --git a/Sources/Jazz2/Multiplayer/INetworkHandler.h b/Sources/Jazz2/Multiplayer/INetworkHandler.h index a1090242..f8f127cf 100644 --- a/Sources/Jazz2/Multiplayer/INetworkHandler.h +++ b/Sources/Jazz2/Multiplayer/INetworkHandler.h @@ -2,7 +2,9 @@ #if defined(WITH_MULTIPLAYER) +#include "ConnectionResult.h" #include "Peer.h" +#include "Reason.h" #include "../../Common.h" namespace Jazz2::Multiplayer @@ -10,8 +12,8 @@ namespace Jazz2::Multiplayer class INetworkHandler { public: - virtual bool OnPeerConnected(const Peer& peer, std::uint32_t clientData) = 0; - virtual void OnPeerDisconnected(const Peer& peer, std::uint32_t reason) = 0; + virtual ConnectionResult OnPeerConnected(const Peer& peer, std::uint32_t clientData) = 0; + virtual void OnPeerDisconnected(const Peer& peer, Reason reason) = 0; virtual void OnPacketReceived(const Peer& peer, std::uint8_t channelId, std::uint8_t* data, std::size_t dataLength) = 0; }; } diff --git a/Sources/Jazz2/Multiplayer/MultiLevelHandler.cpp b/Sources/Jazz2/Multiplayer/MultiLevelHandler.cpp index 1085845e..3d584701 100644 --- a/Sources/Jazz2/Multiplayer/MultiLevelHandler.cpp +++ b/Sources/Jazz2/Multiplayer/MultiLevelHandler.cpp @@ -41,7 +41,7 @@ namespace Jazz2::Multiplayer { MultiLevelHandler::MultiLevelHandler(IRootController* root, NetworkManager* networkManager) : LevelHandler(root), _networkManager(networkManager), _updateTimeLeft(1.0f), _initialUpdateSent(false), - _lastPlayerIndex(-1), _seqNum(0), _seqNumWarped(0) + _lastSpawnedActorId(-1), _seqNum(0), _seqNumWarped(0), _suppressRemoting(false) { _isServer = (networkManager->GetState() == NetworkState::Listening); } @@ -52,13 +52,16 @@ namespace Jazz2::Multiplayer bool MultiLevelHandler::Initialize(const LevelInitialization& levelInit) { - if (!LevelHandler::Initialize(levelInit)) { + _suppressRemoting = true; + bool initialized = LevelHandler::Initialize(levelInit); + _suppressRemoting = false; + if (!initialized) { return false; } for (std::int32_t i = 0; i < countof(levelInit.PlayerCarryOvers); i++) { if (levelInit.PlayerCarryOvers[i].Type != PlayerType::None) { - _lastPlayerIndex = i; + _lastSpawnedActorId = i; } } @@ -86,7 +89,7 @@ namespace Jazz2::Multiplayer if ((_pressedActions & 0xffffffffu) != ((_pressedActions >> 32) & 0xffffffffu)) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)ClientPacketType::PlayerKeyPress); - packet.WriteVariableUint32(_lastPlayerIndex); + packet.WriteVariableUint32(_lastSpawnedActorId); packet.WriteVariableUint32((std::uint32_t)(_pressedActions & 0xffffffffu)); _networkManager->SendToPeer(nullptr, NetworkChannel::UnreliableUpdates, packet.GetBuffer(), packet.GetSize()); } @@ -116,11 +119,11 @@ namespace Jazz2::Multiplayer } if (_isServer) { - std::uint32_t actorCount = (std::uint32_t)_players.size(); + std::uint32_t actorCount = (std::uint32_t)(_players.size() + _remotingActors.size()); MemoryStream packet(5 + actorCount * 9); packet.WriteValue((std::uint8_t)ServerPacketType::UpdateAllActors); - packet.WriteVariableUint32(_players.size()); + packet.WriteVariableUint32(actorCount); for (Actors::Player* player : _players) { Vector2f pos; @@ -163,6 +166,22 @@ namespace Jazz2::Multiplayer packet.WriteValue(flags); } + for (const auto& [remotingActor, remotingActorId] : _remotingActors) { + packet.WriteVariableUint32(remotingActorId); + packet.WriteValue((std::int32_t)(remotingActor->_pos.X * 512.0f)); + packet.WriteValue((std::int32_t)(remotingActor->_pos.Y * 512.0f)); + packet.WriteVariableUint32((std::uint32_t)(remotingActor->_currentTransition != nullptr ? remotingActor->_currentTransition->State : remotingActor->_currentAnimation->State)); + + std::uint8_t flags = 0; + if (remotingActor->IsFacingLeft()) { + flags |= 0x01; + } + if (remotingActor->_renderer.isDrawEnabled()) { + flags |= 0x02; + } + packet.WriteValue(flags); + } + _networkManager->SendToAll(NetworkChannel::UnreliableUpdates, packet.GetBuffer(), packet.GetSize()); } else { if (!_players.empty()) { @@ -188,7 +207,7 @@ namespace Jazz2::Multiplayer MemoryStream packet(20); packet.WriteValue((std::uint8_t)ClientPacketType::PlayerUpdate); - packet.WriteVariableUint32(_lastPlayerIndex); + packet.WriteVariableUint32(_lastSpawnedActorId); packet.WriteVariableUint64(now); packet.WriteValue((std::int32_t)(player->_pos.X * 512.0f)); packet.WriteValue((std::int32_t)(player->_pos.Y * 512.0f)); @@ -229,15 +248,88 @@ namespace Jazz2::Multiplayer void MultiLevelHandler::AddActor(std::shared_ptr actor) { LevelHandler::AddActor(actor); + + if (!_suppressRemoting && _isServer) { + ++_lastSpawnedActorId; + std::uint32_t actorId = _lastSpawnedActorId; + _remotingActors[actor.get()] = actorId; + + const auto& metadataPath = actor->_metadata->Path; + + for (const auto& [peer, peerState] : _peerStates) { + MemoryStream packet(24 + metadataPath.size()); + packet.WriteValue((std::uint8_t)ServerPacketType::CreateRemoteActor); + packet.WriteVariableUint32(actorId); + packet.WriteVariableInt32((std::int32_t)actor->_pos.X); + packet.WriteVariableInt32((std::int32_t)actor->_pos.Y); + packet.WriteVariableInt32((std::int32_t)actor->_renderer.layer()); + packet.WriteVariableUint32((std::uint32_t)metadataPath.size()); + packet.Write(metadataPath.data(), (std::uint32_t)metadataPath.size()); + packet.WriteVariableUint32((std::uint32_t)(actor->_currentTransition != nullptr ? actor->_currentTransition->State : actor->_currentAnimation->State)); + + // TODO: If it fail, it will release the packet which is wrong + _networkManager->SendToPeer(peer, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); + } + } } - std::shared_ptr MultiLevelHandler::PlaySfx(AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) + std::shared_ptr MultiLevelHandler::PlaySfx(Actors::ActorBase* self, const StringView& identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) { - return LevelHandler::PlaySfx(buffer, pos, sourceRelative, gain, pitch); + if (_isServer) { + std::uint32_t actorId; + auto it = _remotingActors.find(self); + if (auto* player = dynamic_cast(self)) { + actorId = player->_playerIndex; + } else if (it != _remotingActors.end()) { + actorId = it->second; + } else { + actorId = UINT32_MAX; + } + + if (actorId != UINT32_MAX) { + for (const auto& [peer, peerState] : _peerStates) { + if (self == peerState.Player) { + continue; + } + + MemoryStream packet(9 + identifier.size()); + packet.WriteValue((std::uint8_t)ServerPacketType::PlaySfx); + packet.WriteVariableUint32(actorId); + // TODO: sourceRelative + // TODO: looping + // TODO: gain + // TODO: pitch + packet.WriteVariableUint32((std::uint32_t)identifier.size()); + packet.Write(identifier.data(), (std::uint32_t)identifier.size()); + + // TODO: If it fails, it will release the packet which is wrong + _networkManager->SendToPeer(peer, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); + } + } + } + + return LevelHandler::PlaySfx(self, identifier, buffer, pos, sourceRelative, gain, pitch); } std::shared_ptr MultiLevelHandler::PlayCommonSfx(const StringView& identifier, const Vector3f& pos, float gain, float pitch) { + if (_isServer) { + for (const auto& [peer, peerState] : _peerStates) { + MemoryStream packet(10 + identifier.size()); + packet.WriteValue((std::uint8_t)ServerPacketType::PlayCommonSfx); + packet.WriteVariableInt32((std::int32_t)pos.X); + packet.WriteVariableInt32((std::int32_t)pos.Y); + // TODO: looping + // TODO: gain + // TODO: pitch + packet.WriteVariableUint32((std::uint32_t)identifier.size()); + packet.Write(identifier.data(), (std::uint32_t)identifier.size()); + + // TODO: If it fails, it will release the packet which is wrong + _networkManager->SendToPeer(peer, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); + } + } + return LevelHandler::PlayCommonSfx(identifier, pos, gain, pitch); } @@ -514,8 +606,8 @@ namespace Jazz2::Multiplayer _playerStates.erase(playerIndex); - for (auto& pair : _peerStates) { - if (pair.first == peer) { + for (auto& [otherPeer, otherPeerState] : _peerStates) { + if (otherPeer == peer) { continue; } @@ -524,7 +616,7 @@ namespace Jazz2::Multiplayer packet.WriteVariableUint32(playerIndex); // TODO: If it fails, it will release the packet which is wrong - _networkManager->SendToPeer(pair.first, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); + _networkManager->SendToPeer(otherPeer, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); } } return true; @@ -538,6 +630,23 @@ namespace Jazz2::Multiplayer if (_isServer) { auto packetType = (ClientPacketType)data[0]; switch (packetType) { + case ClientPacketType::Auth: { + std::uint8_t flags = 0; + if (PreferencesCache::EnableReforged) { + flags |= 0x01; + } + + MemoryStream packet(10 + _episodeName.size() + _levelFileName.size()); + packet.WriteValue((std::uint8_t)ServerPacketType::LoadLevel); + packet.WriteValue(flags); + packet.WriteVariableUint32(_episodeName.size()); + packet.Write(_episodeName.data(), _episodeName.size()); + packet.WriteVariableUint32(_levelFileName.size()); + packet.Write(_levelFileName.data(), _levelFileName.size()); + + _networkManager->SendToPeer(peer, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); + return true; + } case ClientPacketType::LevelReady: { // TODO Vector2 spawnPosition = EventMap()->GetSpawnPosition(PlayerType::Jazz); @@ -545,8 +654,8 @@ namespace Jazz2::Multiplayer return true; } - ++_lastPlayerIndex; - std::int32_t playerIndex = _lastPlayerIndex; + ++_lastSpawnedActorId; + std::int32_t playerIndex = _lastSpawnedActorId; std::shared_ptr player = std::make_shared(); std::uint8_t playerParams[2] = { (std::uint8_t)PlayerType::Spaz, (std::uint8_t)playerIndex }; @@ -562,7 +671,9 @@ namespace Jazz2::Multiplayer _peerStates[peer] = PeerState(ptr); _playerStates[playerIndex] = PlayerState(player->_pos, player->_speed); + _suppressRemoting = true; AddActor(player); + _suppressRemoting = false; // Spawn the player also on the remote side { @@ -583,43 +694,63 @@ namespace Jazz2::Multiplayer _networkManager->SendToPeer(peer, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); } - // TODO: Create all remote players - String metadataPath = "Interactive/PlayerJazz"_s; - for (Actors::Player* otherPlayer : _players) { if (otherPlayer == ptr) { continue; } - MemoryStream packet(20 + metadataPath.size()); + const auto& metadataPath = otherPlayer->_metadata->Path; + + MemoryStream packet(24 + metadataPath.size()); packet.WriteValue((std::uint8_t)ServerPacketType::CreateRemoteActor); packet.WriteVariableUint32(otherPlayer->_playerIndex); packet.WriteVariableInt32((std::int32_t)otherPlayer->_pos.X); packet.WriteVariableInt32((std::int32_t)otherPlayer->_pos.Y); packet.WriteVariableInt32((std::int32_t)otherPlayer->_renderer.layer()); - packet.WriteVariableUint32((std::uint32_t)metadataPath.size()); // TODO + packet.WriteVariableUint32((std::uint32_t)metadataPath.size()); packet.Write(metadataPath.data(), (std::uint32_t)metadataPath.size()); + packet.WriteVariableUint32((std::uint32_t)(otherPlayer->_currentTransition != nullptr ? otherPlayer->_currentTransition->State : otherPlayer->_currentAnimation->State)); // TODO: If it fail, it will release the packet which is wrong _networkManager->SendToPeer(peer, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); } - for (auto& pair : _peerStates) { - if (pair.first == peer) { + for (const auto& [remotingActor, remotingActorId] : _remotingActors) { + const auto& metadataPath = remotingActor->_metadata->Path; + + MemoryStream packet(24 + metadataPath.size()); + packet.WriteValue((std::uint8_t)ServerPacketType::CreateRemoteActor); + packet.WriteVariableUint32(remotingActorId); + packet.WriteVariableInt32((std::int32_t)remotingActor->_pos.X); + packet.WriteVariableInt32((std::int32_t)remotingActor->_pos.Y); + packet.WriteVariableInt32((std::int32_t)remotingActor->_renderer.layer()); + packet.WriteVariableUint32((std::uint32_t)metadataPath.size()); + packet.Write(metadataPath.data(), (std::uint32_t)metadataPath.size()); + packet.WriteVariableUint32((std::uint32_t)(remotingActor->_currentTransition != nullptr ? remotingActor->_currentTransition->State : remotingActor->_currentAnimation->State)); + + // TODO: If it fail, it will release the packet which is wrong + _networkManager->SendToPeer(peer, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); + } + + for (const auto& [otherPeer, otherPeerState] : _peerStates) { + if (otherPeer == peer) { continue; } - MemoryStream packet(20 + metadataPath.size()); + const auto& metadataPath = player->_metadata->Path; + + MemoryStream packet(24 + metadataPath.size()); packet.WriteValue((std::uint8_t)ServerPacketType::CreateRemoteActor); packet.WriteVariableUint32(playerIndex); packet.WriteVariableInt32((std::int32_t)player->_pos.X); packet.WriteVariableInt32((std::int32_t)player->_pos.Y); packet.WriteVariableInt32((std::int32_t)player->_renderer.layer()); - packet.WriteVariableUint32((std::uint32_t)metadataPath.size()); // TODO + packet.WriteVariableUint32((std::uint32_t)metadataPath.size()); packet.Write(metadataPath.data(), (std::uint32_t)metadataPath.size()); + packet.WriteVariableUint32((std::uint32_t)(player->_currentTransition != nullptr ? player->_currentTransition->State : player->_currentAnimation->State)); // TODO: If it fails, it will release the packet which is wrong - _networkManager->SendToPeer(pair.first, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); + _networkManager->SendToPeer(otherPeer, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); } return true; } @@ -753,13 +884,45 @@ namespace Jazz2::Multiplayer WeaponType weaponType = (WeaponType)packet.ReadValue(); LOGW("[TODO] Player %i fires weapon %i", playerIndex, (std::int32_t)weaponType); - break; + return true; } } - } else { auto packetType = (ServerPacketType)data[0]; switch (packetType) { + case ServerPacketType::PlaySfx: { + MemoryStream packet(data + 1, dataLength - 1); + std::uint32_t actorId = packet.ReadVariableUint32(); + std::uint32_t identifierLength = packet.ReadVariableUint32(); + String identifier = String(NoInit, identifierLength); + packet.Read(identifier.data(), identifierLength); + + auto it = _remoteActors.find(actorId); + if (it != _remoteActors.end()) { + // TODO: gain, pitch, ... + it->second->PlaySfx(identifier); + } + break; + } + case ServerPacketType::PlayCommonSfx: { + MemoryStream packet(data + 1, dataLength - 1); + std::int32_t posX = packet.ReadVariableInt32(); + std::int32_t posY = packet.ReadVariableInt32(); + std::uint32_t identifierLength = packet.ReadVariableUint32(); + String identifier = String(NoInit, identifierLength); + packet.Read(identifier.data(), identifierLength); + // TODO: gain, pitch, ... + PlayCommonSfx(identifier, Vector3f(posX, posY, 0.0f)); + break; + } + case ServerPacketType::ShowMessage: { + MemoryStream packet(data + 1, dataLength - 1); + std::uint32_t messageLength = packet.ReadVariableUint32(); + String message = String(NoInit, messageLength); + packet.Read(message.data(), messageLength); + _hud->ShowLevelText(message); + break; + } case ServerPacketType::CreateControllablePlayer: { MemoryStream packet(data + 1, dataLength - 1); std::uint32_t playerIndex = packet.ReadVariableUint32(); @@ -782,35 +945,41 @@ namespace Jazz2::Multiplayer _players.push_back(ptr); AddActor(player); - _lastPlayerIndex = playerIndex; + _lastSpawnedActorId = playerIndex; return true; } case ServerPacketType::CreateRemoteActor: { MemoryStream packet(data + 1, dataLength - 1); - std::uint32_t index = packet.ReadVariableUint32(); + std::uint32_t actorId = packet.ReadVariableUint32(); std::int32_t posX = packet.ReadVariableInt32(); std::int32_t posY = packet.ReadVariableInt32(); std::int32_t posZ = packet.ReadVariableInt32(); std::uint32_t metadataLength = packet.ReadVariableUint32(); String metadataPath = String(NoInit, metadataLength); packet.Read(metadataPath.data(), metadataLength); + std::uint32_t anim = packet.ReadVariableUint32(); std::shared_ptr remoteActor = std::make_shared(); remoteActor->OnActivated(Actors::ActorActivationDetails(this, Vector3i(posX, posY, posZ))); + remoteActor->AssignMetadata(metadataPath, (AnimState)anim); - _remoteActors[index] = remoteActor; + _remoteActors[actorId] = remoteActor; AddActor(std::static_pointer_cast(remoteActor)); + + LOGD("Remote actor %u created on [%i;%i] with metadata \"%s\"", actorId, posX, posY, metadataPath.data()); return true; } case ServerPacketType::DestroyRemoteActor: { MemoryStream packet(data + 1, dataLength - 1); - std::uint32_t index = packet.ReadVariableUint32(); + std::uint32_t actorId = packet.ReadVariableUint32(); - auto it = _remoteActors.find(index); + auto it = _remoteActors.find(actorId); if (it != _remoteActors.end()) { it->second->SetState(Actors::ActorState::IsDestroyed, true); _remoteActors.erase(it); } + + LOGD("Remote actor %u destroyed", actorId); return true; } case ServerPacketType::UpdateAllActors: { @@ -848,7 +1017,7 @@ namespace Jazz2::Multiplayer case ServerPacketType::PlayerMoveInstantly: { MemoryStream packet(data + 1, dataLength - 1); std::uint32_t playerIndex = packet.ReadVariableUint32(); - if (_lastPlayerIndex != playerIndex) { + if (_lastSpawnedActorId != playerIndex) { return true; } @@ -865,15 +1034,11 @@ namespace Jazz2::Multiplayer MemoryStream packet(data + 1, dataLength - 1); std::uint32_t playerIndex = packet.ReadVariableUint32(); std::uint64_t seqNum = packet.ReadVariableUint64(); - if (_lastPlayerIndex == playerIndex && _seqNumWarped == seqNum) { + if (_lastSpawnedActorId == playerIndex && _seqNumWarped == seqNum) { _seqNumWarped = 0; } return true; } - case ServerPacketType::PlaySfx: { - // TODO - return true; - } } } @@ -918,6 +1083,33 @@ namespace Jazz2::Multiplayer return LevelHandler::BeginPlayMusic(path, setDefault, forceReload); } + void MultiLevelHandler::BeforeActorDestroyed(Actors::ActorBase* actor) + { + auto it = _remotingActors.find(actor); + if (it == _remotingActors.end()) { + return; + } + + for (auto& [peer, peerState] : _peerStates) { + MemoryStream packet(5); + packet.WriteValue((std::uint8_t)ServerPacketType::DestroyRemoteActor); + packet.WriteVariableUint32(it->second); + + // TODO: If it fails, it will release the packet which is wrong + _networkManager->SendToPeer(peer, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); + } + + _remotingActors.erase(it); + } + + void MultiLevelHandler::ProcessEvents(float timeMult) + { + // Process events only by server + if (_isServer) { + LevelHandler::ProcessEvents(timeMult); + } + } + void MultiLevelHandler::UpdatePlayerLocalPos(Actors::Player* player, PlayerState& playerState, float timeMult) { if (playerState.WarpTimeLeft > 0.0f || !player->_controllable || !player->GetState(Actors::ActorState::CollideWithTileset)) { @@ -1023,6 +1215,10 @@ namespace Jazz2::Multiplayer playerState.Flags = (flags & ~PlayerFlags::JustWarped); } + MultiLevelHandler::PlayerState::PlayerState() + { + } + MultiLevelHandler::PlayerState::PlayerState(const Vector2f& pos, const Vector2f& speed) : StateBufferPos(0), Flags(PlayerFlags::None), PressedKeys(0), WarpSeqNum(0), WarpTimeLeft(0.0f), DeviationTime(0.0f) { diff --git a/Sources/Jazz2/Multiplayer/MultiLevelHandler.h b/Sources/Jazz2/Multiplayer/MultiLevelHandler.h index 2c9c6d98..90e0ab41 100644 --- a/Sources/Jazz2/Multiplayer/MultiLevelHandler.h +++ b/Sources/Jazz2/Multiplayer/MultiLevelHandler.h @@ -54,7 +54,7 @@ namespace Jazz2::Multiplayer void AddActor(std::shared_ptr actor) override; - std::shared_ptr PlaySfx(AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain = 1.0f, float pitch = 1.0f) override; + std::shared_ptr PlaySfx(Actors::ActorBase* self, const StringView& identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) override; std::shared_ptr PlayCommonSfx(const StringView& identifier, const Vector3f& pos, float gain = 1.0f, float pitch = 1.0f) override; void WarpCameraToTarget(const std::shared_ptr& actor, bool fast = false) override; bool IsPositionEmpty(Actors::ActorBase* self, const AABBf& aabb, TileCollisionParams& params, Actors::ActorBase** collider) override; @@ -96,6 +96,10 @@ namespace Jazz2::Multiplayer bool OnPeerDisconnected(const Peer& peer); bool OnPacketReceived(const Peer& peer, std::uint8_t channelId, std::uint8_t* data, std::size_t dataLength); + protected: + void BeforeActorDestroyed(Actors::ActorBase* actor) override; + void ProcessEvents(float timeMult) override; + private: struct PeerState { Actors::Player* Player; @@ -134,7 +138,7 @@ namespace Jazz2::Multiplayer float WarpTimeLeft; float DeviationTime; - PlayerState() {} + PlayerState(); PlayerState(const Vector2f& pos, const Vector2f& speed); }; @@ -147,9 +151,11 @@ namespace Jazz2::Multiplayer HashMap _peerStates; // Server: Per peer state HashMap _playerStates; // Server: Per (remote) player state HashMap> _remoteActors; // Client: Actor ID -> Remote Actor created by server - std::uint32_t _lastPlayerIndex; // Server: last assigned ID, Client: ID assigned by server + HashMap _remotingActors; // Server: Local Actor created by server -> Actor ID + std::uint32_t _lastSpawnedActorId; // Server: last assigned actor/player ID, Client: ID assigned by server std::uint64_t _seqNum; // Client: sequence number of the last update std::uint64_t _seqNumWarped; // Client: set to _seqNum from HandlePlayerWarped() when warped + bool _suppressRemoting; // Server: if true, actor will not be automatically remoted to other players void UpdatePlayerLocalPos(Actors::Player* player, PlayerState& playerState, float timeMult); void OnRemotePlayerPosReceived(PlayerState& playerState, const Vector2f& pos, const Vector2f speed, PlayerFlags flags); diff --git a/Sources/Jazz2/Multiplayer/NetworkManager.cpp b/Sources/Jazz2/Multiplayer/NetworkManager.cpp index 871be7bc..20766700 100644 --- a/Sources/Jazz2/Multiplayer/NetworkManager.cpp +++ b/Sources/Jazz2/Multiplayer/NetworkManager.cpp @@ -20,8 +20,6 @@ #include -#define MAX_CLIENTS 64 - namespace Jazz2::Multiplayer { NetworkManager::NetworkManager() @@ -50,8 +48,6 @@ namespace Jazz2::Multiplayer _host = enet_host_create(nullptr, 1, (std::size_t)NetworkChannel::Count, 0, 0); RETURNF_ASSERT_MSG(_host != nullptr, "Failed to create client"); - //enet_host_compress_with_range_coder(host); - _state = NetworkState::Connecting; ENetAddress addr = { }; @@ -84,7 +80,7 @@ namespace Jazz2::Multiplayer addr.host = ENET_HOST_ANY; addr.port = port; - _host = enet_host_create(&addr, MAX_CLIENTS, (std::size_t)NetworkChannel::Count, 0, 0); + _host = enet_host_create(&addr, MaxPeerCount, (std::size_t)NetworkChannel::Count, 0, 0); RETURNF_ASSERT_MSG(_host != nullptr, "Failed to create a server"); _handler = handler; @@ -99,16 +95,9 @@ namespace Jazz2::Multiplayer return; } - for (auto& peer : _peers) { - enet_peer_disconnect_now(peer, 0); - } - _state = NetworkState::None; _thread.Join(); - // Should be already destroyed by the thread - //enet_host_destroy(_host); - _host = nullptr; } @@ -178,6 +167,11 @@ namespace Jazz2::Multiplayer _lock.Unlock(); } + void NetworkManager::KickClient(const Peer& peer, Reason reason) + { + enet_peer_disconnect_now(peer._enet, (std::uint32_t)reason); + } + void NetworkManager::OnClientThread(void* param) { NetworkManager* _this = reinterpret_cast(param); @@ -199,12 +193,15 @@ namespace Jazz2::Multiplayer n--; } + Reason reason; if (n <= 0) { LOGE("Failed to connect to the server"); _this->_state = NetworkState::None; + reason = Reason::ConnectionTimedOut; } else { _this->_state = NetworkState::Connected; handler->OnPeerConnected(ev.peer, ev.data); + reason = Reason::Unknown; while (_this->_state != NetworkState::None) { _this->_lock.Lock(); @@ -213,9 +210,10 @@ namespace Jazz2::Multiplayer if (result <= 0) { if (result < 0) { LOGE("enet_host_service() returned %i", result); + reason = Reason::ConnectionLost; break; } - Timer::sleep(4); + Timer::sleep(ProcessingIntervalMs); continue; } @@ -224,23 +222,22 @@ namespace Jazz2::Multiplayer handler->OnPacketReceived(ev.peer, ev.channelID, ev.packet->data, ev.packet->dataLength); enet_packet_destroy(ev.packet); break; - case ENET_EVENT_TYPE_DISCONNECT: _this->_state = NetworkState::None; + reason = (Reason)ev.data; break; - case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: _this->_state = NetworkState::None; + reason = Reason::ConnectionLost; break; } } } - handler->OnPeerDisconnected(_this->_peers[0], 0); + handler->OnPeerDisconnected(_this->_peers[0], reason); for (ENetPeer* peer : _this->_peers) { - //enet_peer_reset(peer); - enet_peer_disconnect(peer, 1); + enet_peer_disconnect_now(peer, (std::uint32_t)Reason::Disconnected); } _this->_peers.clear(); @@ -250,7 +247,7 @@ namespace Jazz2::Multiplayer _this->_thread.Detach(); - LOGD("Client thread exited"); + LOGD("Client thread exited (%u)", (std::uint32_t)reason); } void NetworkManager::OnServerThread(void* param) @@ -272,13 +269,13 @@ namespace Jazz2::Multiplayer _this->_lock.Lock(); for (auto& peer : _this->_peers) { - handler->OnPeerDisconnected(peer, 0); + handler->OnPeerDisconnected(peer, Reason::ConnectionLost); } _this->_peers.clear(); ENetAddress addr = _this->_host->address; enet_host_destroy(_this->_host); - host = enet_host_create(&addr, MAX_CLIENTS, (std::size_t)NetworkChannel::Count, 0, 0); + host = enet_host_create(&addr, MaxPeerCount, (std::size_t)NetworkChannel::Count, 0, 0); _this->_host = host; _this->_lock.Unlock(); @@ -288,37 +285,35 @@ namespace Jazz2::Multiplayer break; } } - Timer::sleep(4); + Timer::sleep(ProcessingIntervalMs); continue; } switch (ev.type) { - case ENET_EVENT_TYPE_CONNECT: - if (handler->OnPeerConnected(ev.peer, ev.data)) { + case ENET_EVENT_TYPE_CONNECT: { + ConnectionResult result = handler->OnPeerConnected(ev.peer, ev.data); + if (result.IsSuccessful()) { _this->_peers.push_back(ev.peer); } else { - enet_peer_disconnect_now(ev.peer, 1); + enet_peer_disconnect_now(ev.peer, (std::uint32_t)result.FailureReason); } break; - + } case ENET_EVENT_TYPE_RECEIVE: handler->OnPacketReceived(ev.peer, ev.channelID, ev.packet->data, ev.packet->dataLength); enet_packet_destroy(ev.packet); break; - case ENET_EVENT_TYPE_DISCONNECT: - handler->OnPeerDisconnected(ev.peer, ev.data); + handler->OnPeerDisconnected(ev.peer, (Reason)ev.data); break; - case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: - handler->OnPeerDisconnected(ev.peer, 0); + handler->OnPeerDisconnected(ev.peer, Reason::ConnectionLost); break; } } for (ENetPeer* peer : _this->_peers) { - //enet_peer_reset(peer); - enet_peer_disconnect(peer, 1); + enet_peer_disconnect_now(peer, (std::uint32_t)Reason::ServerStopped); } _this->_peers.clear(); diff --git a/Sources/Jazz2/Multiplayer/NetworkManager.h b/Sources/Jazz2/Multiplayer/NetworkManager.h index d9931907..a0a6c7f3 100644 --- a/Sources/Jazz2/Multiplayer/NetworkManager.h +++ b/Sources/Jazz2/Multiplayer/NetworkManager.h @@ -3,6 +3,7 @@ #if defined(WITH_MULTIPLAYER) #include "Peer.h" +#include "Reason.h" #include "../../Common.h" #include "../../nCine/Threading/Thread.h" #include "../../nCine/Threading/ThreadSync.h" @@ -48,11 +49,15 @@ namespace Jazz2::Multiplayer void SendToPeer(const Peer& peer, NetworkChannel channel, const std::uint8_t* data, std::size_t dataLength); void SendToAll(NetworkChannel channel, const std::uint8_t* data, std::size_t dataLength); + void KickClient(const Peer& peer, Reason reason); private: NetworkManager(const NetworkManager&) = delete; NetworkManager& operator=(const NetworkManager&) = delete; + static constexpr std::size_t MaxPeerCount = 64; + static constexpr std::uint32_t ProcessingIntervalMs = 4; + bool _initialized; _ENetHost* _host; Thread _thread; diff --git a/Sources/Jazz2/Multiplayer/PacketTypes.h b/Sources/Jazz2/Multiplayer/PacketTypes.h index 99f8bdc5..7a21711f 100644 --- a/Sources/Jazz2/Multiplayer/PacketTypes.h +++ b/Sources/Jazz2/Multiplayer/PacketTypes.h @@ -32,6 +32,7 @@ namespace Jazz2::Multiplayer LoadLevel, PlaySfx, + PlayCommonSfx, ShowMessage, OverrideLevelText, SetTrigger, diff --git a/Sources/Jazz2/Multiplayer/Reason.h b/Sources/Jazz2/Multiplayer/Reason.h new file mode 100644 index 00000000..ef31fbee --- /dev/null +++ b/Sources/Jazz2/Multiplayer/Reason.h @@ -0,0 +1,24 @@ +#pragma once + +#if defined(WITH_MULTIPLAYER) + +#include "../../Common.h" + +namespace Jazz2::Multiplayer +{ + enum class Reason : std::uint32_t + { + Unknown, + Disconnected, + IncompatibleVersion, + ServerIsFull, + ServerNotReady, + ServerStopped, + ConnectionLost, + ConnectionTimedOut, + Kicked, + Banned + }; +} + +#endif \ No newline at end of file diff --git a/Sources/Jazz2/Resources.h b/Sources/Jazz2/Resources.h index 6386f4a5..d6cca88a 100644 --- a/Sources/Jazz2/Resources.h +++ b/Sources/Jazz2/Resources.h @@ -93,6 +93,7 @@ namespace Jazz2 struct Metadata { + String Path; MetadataFlags Flags; SmallVector Animations; HashMap Sounds; diff --git a/Sources/Jazz2/UI/Menu/IMenuContainer.h b/Sources/Jazz2/UI/Menu/IMenuContainer.h index 7c55bd36..7a7f2242 100644 --- a/Sources/Jazz2/UI/Menu/IMenuContainer.h +++ b/Sources/Jazz2/UI/Menu/IMenuContainer.h @@ -34,12 +34,12 @@ namespace Jazz2::UI::Menu virtual ~IMenuContainer() { } template - void SwitchToSection(Params&&... args) + T* SwitchToSection(Params&&... args) { - SwitchToSectionDirect(std::make_unique(std::forward(args)...)); + return static_cast(SwitchToSectionDirect(std::make_unique(std::forward(args)...))); } - virtual void SwitchToSectionDirect(std::unique_ptr section) = 0; + virtual MenuSection* SwitchToSectionDirect(std::unique_ptr section) = 0; virtual void LeaveSection() = 0; virtual void ChangeLevel(Jazz2::LevelInitialization&& levelInit) = 0; #if defined(WITH_MULTIPLAYER) diff --git a/Sources/Jazz2/UI/Menu/InGameMenu.cpp b/Sources/Jazz2/UI/Menu/InGameMenu.cpp index 26b0f459..bee2117e 100644 --- a/Sources/Jazz2/UI/Menu/InGameMenu.cpp +++ b/Sources/Jazz2/UI/Menu/InGameMenu.cpp @@ -195,7 +195,7 @@ namespace Jazz2::UI::Menu return true; } - void InGameMenu::SwitchToSectionDirect(std::unique_ptr section) + MenuSection* InGameMenu::SwitchToSectionDirect(std::unique_ptr section) { if (!_sections.empty()) { auto& lastSection = _sections.back(); @@ -209,6 +209,8 @@ namespace Jazz2::UI::Menu Recti clipRectangle = currentSection->GetClipRectangle(_canvasBackground->ViewSize); _root->_upscalePass.SetClipRectangle(clipRectangle); } + + return currentSection.get(); } void InGameMenu::LeaveSection() diff --git a/Sources/Jazz2/UI/Menu/InGameMenu.h b/Sources/Jazz2/UI/Menu/InGameMenu.h index 5ba58f50..73b3f067 100644 --- a/Sources/Jazz2/UI/Menu/InGameMenu.h +++ b/Sources/Jazz2/UI/Menu/InGameMenu.h @@ -28,7 +28,7 @@ namespace Jazz2::UI::Menu void OnTouchEvent(const nCine::TouchEvent& event); void OnInitializeViewport(int32_t width, int32_t height); - void SwitchToSectionDirect(std::unique_ptr section) override; + MenuSection* SwitchToSectionDirect(std::unique_ptr section) override; void LeaveSection() override; void ChangeLevel(Jazz2::LevelInitialization&& levelInit) override; #if defined(WITH_MULTIPLAYER) diff --git a/Sources/Jazz2/UI/Menu/MainMenu.cpp b/Sources/Jazz2/UI/Menu/MainMenu.cpp index 9af47147..0fe216ff 100644 --- a/Sources/Jazz2/UI/Menu/MainMenu.cpp +++ b/Sources/Jazz2/UI/Menu/MainMenu.cpp @@ -311,7 +311,7 @@ namespace Jazz2::UI::Menu return true; } - void MainMenu::SwitchToSectionDirect(std::unique_ptr section) + MenuSection* MainMenu::SwitchToSectionDirect(std::unique_ptr section) { if (!_sections.empty()) { auto& lastSection = _sections.back(); @@ -325,6 +325,8 @@ namespace Jazz2::UI::Menu Recti clipRectangle = currentSection->GetClipRectangle(_canvasBackground->ViewSize); _upscalePass.SetClipRectangle(clipRectangle); } + + return currentSection.get(); } void MainMenu::LeaveSection() diff --git a/Sources/Jazz2/UI/Menu/MainMenu.h b/Sources/Jazz2/UI/Menu/MainMenu.h index a48caa5f..908df962 100644 --- a/Sources/Jazz2/UI/Menu/MainMenu.h +++ b/Sources/Jazz2/UI/Menu/MainMenu.h @@ -44,7 +44,7 @@ namespace Jazz2::UI::Menu void OnKeyReleased(const KeyboardEvent& event) override; void OnTouchEvent(const nCine::TouchEvent& event) override; - void SwitchToSectionDirect(std::unique_ptr section) override; + MenuSection* SwitchToSectionDirect(std::unique_ptr section) override; void LeaveSection() override; void ChangeLevel(Jazz2::LevelInitialization&& levelInit) override; #if defined(WITH_MULTIPLAYER) diff --git a/Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp b/Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp index 640db442..e202bf00 100644 --- a/Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp +++ b/Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp @@ -5,11 +5,16 @@ using namespace Jazz2::UI::Menu::Resources; namespace Jazz2::UI::Menu { - SimpleMessageSection::SimpleMessageSection(Message message) + SimpleMessageSection::SimpleMessageSection(const StringView& message) : _message(message) { } + SimpleMessageSection::SimpleMessageSection(String&& message) + : _message(std::move(message)) + { + } + void SimpleMessageSection::OnUpdate(float timeMult) { if (_root->ActionHit(PlayerActions::Menu) || _root->ActionHit(PlayerActions::Fire)) { @@ -24,27 +29,20 @@ namespace Jazz2::UI::Menu Vector2i viewSize = canvas->ViewSize; Vector2f center = Vector2f(viewSize.X * 0.5f, viewSize.Y * 0.5f); - constexpr float topLine = 131.0f; + constexpr float topLine = 160.f; _root->DrawElement(MenuDim, center.X, topLine - 2.0f, IMenuContainer::BackgroundLayer, Alignment::Top, Colorf::Black, Vector2f(680.0f, 200.0f), Vector4f(1.0f, 0.0f, 0.7f, 0.0f)); _root->DrawElement(MenuLine, 0, center.X, topLine, IMenuContainer::MainLayer, Alignment::Center, Colorf::White, 1.6f); - int32_t charOffset = 0; - _root->DrawStringShadow(_("Error"), charOffset, center.X, topLine - 21.0f, IMenuContainer::FontLayer, - Alignment::Center, Colorf(0.46f, 0.46f, 0.46f, 0.5f), 0.9f, 0.7f, 1.1f, 1.1f, 0.4f, 0.9f); - - switch (_message) { - case Message::CannotLoadLevel: - _root->DrawStringShadow(_("\f[c:0x704a4a]Cannot load specified level!\f[c]\n\nMake sure all necessary files\nare accessible and try it again."), charOffset, center.X, topLine + 40.0f, IMenuContainer::FontLayer, - Alignment::Top, Font::DefaultColor, 0.9f, 0.4f, 0.6f, 0.6f, 0.6f, 0.9f, 1.2f); - break; - } + std::int32_t charOffset = 0; + _root->DrawStringShadow(_message, charOffset, center.X, topLine - 40.0f, IMenuContainer::FontLayer, + Alignment::Top, Font::DefaultColor, 0.9f, 0.4f, 0.6f, 0.6f, 0.6f, 0.9f, 1.2f); } void SimpleMessageSection::OnTouchEvent(const nCine::TouchEvent& event, const Vector2i& viewSize) { if (event.type == TouchEventType::Down) { - int32_t pointerIndex = event.findPointerIndex(event.actionIndex); + std::int32_t pointerIndex = event.findPointerIndex(event.actionIndex); if (pointerIndex != -1) { float y = event.pointers[pointerIndex].y * (float)viewSize.Y; if (y < 80.0f) { diff --git a/Sources/Jazz2/UI/Menu/SimpleMessageSection.h b/Sources/Jazz2/UI/Menu/SimpleMessageSection.h index 0e15dfa7..d1fe2e75 100644 --- a/Sources/Jazz2/UI/Menu/SimpleMessageSection.h +++ b/Sources/Jazz2/UI/Menu/SimpleMessageSection.h @@ -2,23 +2,22 @@ #include "MenuSection.h" +#include +#include + namespace Jazz2::UI::Menu { class SimpleMessageSection : public MenuSection { public: - enum class Message { - Unknown, - CannotLoadLevel - }; - - SimpleMessageSection(Message message); + SimpleMessageSection(const StringView& message); + SimpleMessageSection(String&& message); void OnUpdate(float timeMult) override; void OnDraw(Canvas* canvas) override; void OnTouchEvent(const nCine::TouchEvent& event, const Vector2i& viewSize) override; private: - Message _message; + String _message; }; } \ No newline at end of file diff --git a/Sources/Main.cpp b/Sources/Main.cpp index 71c323ec..7b7ac059 100644 --- a/Sources/Main.cpp +++ b/Sources/Main.cpp @@ -103,8 +103,8 @@ class GameEventHandler : public IAppEventHandler, public IInputEventHandler, pub bool ConnectToServer(const StringView& address, std::uint16_t port) override; bool CreateServer(std::uint16_t port) override; - bool OnPeerConnected(const Peer& peer, std::uint32_t clientData) override; - void OnPeerDisconnected(const Peer& peer, std::uint32_t reason) override; + ConnectionResult OnPeerConnected(const Peer& peer, std::uint32_t clientData) override; + void OnPeerDisconnected(const Peer& peer, Reason reason) override; void OnPacketReceived(const Peer& peer, std::uint8_t channelId, std::uint8_t* data, std::size_t dataLength) override; #endif @@ -471,7 +471,7 @@ void GameEventHandler::ChangeLevel(LevelInitialization&& levelInit) UpdateRichPresence(levelInit); } else { auto mainMenu = std::make_unique(this, false); - mainMenu->SwitchToSection(Menu::SimpleMessageSection::Message::CannotLoadLevel); + mainMenu->SwitchToSection(_("\f[c:0x704a4a]Cannot load specified level!\f[c]\n\n\nMake sure all necessary files\nare accessible and try it again.")); newHandler = std::move(mainMenu); } } else { @@ -509,7 +509,7 @@ void GameEventHandler::ChangeLevel(LevelInitialization&& levelInit) UpdateRichPresence(levelInit); } else { auto mainMenu = std::make_unique(this, false); - mainMenu->SwitchToSection(Menu::SimpleMessageSection::Message::CannotLoadLevel); + mainMenu->SwitchToSection(_("\f[c:0x704a4a]Cannot load specified level!\f[c]\n\n\nMake sure all necessary files\nare accessible and try it again.")); newHandler = std::move(mainMenu); } } @@ -547,7 +547,8 @@ bool GameEventHandler::CreateServer(std::uint16_t port) } InvokeAsync([this]() { - LevelInitialization levelInit("unknown", "race3", GameDifficulty::Multiplayer, true, false, PlayerType::Jazz); + // TODO: Hardcoded level + LevelInitialization levelInit("prince", "01_castle1", GameDifficulty::Multiplayer, true, false, PlayerType::Jazz); auto levelHandler = std::make_unique(this, _networkManager.get()); levelHandler->Initialize(levelInit); SetStateHandler(std::move(levelHandler)); @@ -556,14 +557,14 @@ bool GameEventHandler::CreateServer(std::uint16_t port) return true; } -bool GameEventHandler::OnPeerConnected(const Peer& peer, std::uint32_t clientData) +ConnectionResult GameEventHandler::OnPeerConnected(const Peer& peer, std::uint32_t clientData) { LOGI("Peer connected"); if (_networkManager->GetState() == NetworkState::Listening) { if ((clientData & 0xFF000000) != 0xCA000000 || (clientData & 0x00FFFFFF) > MultiplayerProtocolVersion) { // Connected client is newer than server, reject it - return false; + return Reason::IncompatibleVersion; } } else { // TODO: Auth packet @@ -574,9 +575,9 @@ bool GameEventHandler::OnPeerConnected(const Peer& peer, std::uint32_t clientDat return true; } -void GameEventHandler::OnPeerDisconnected(const Peer& peer, std::uint32_t reason) +void GameEventHandler::OnPeerDisconnected(const Peer& peer, Reason reason) { - LOGI("Peer disconnected"); + LOGI("Peer disconnected (%u)", (std::uint32_t)reason); if (auto multiLevelHandler = dynamic_cast(_currentHandler.get())) { if (multiLevelHandler->OnPeerDisconnected(peer)) { @@ -585,20 +586,38 @@ void GameEventHandler::OnPeerDisconnected(const Peer& peer, std::uint32_t reason } if (_networkManager != nullptr && _networkManager->GetState() != NetworkState::Listening) { - // TODO: Show error message only if not initiated by the player - GoToMainMenu(false); + InvokeAsync([this, reason]() { +#if defined(WITH_MULTIPLAYER) + _networkManager = nullptr; +#endif + Menu::MainMenu* mainMenu; + if (mainMenu = dynamic_cast(_currentHandler.get())) { + mainMenu->Reset(); + } else { + auto newHandler = std::make_unique(this, false); + mainMenu = newHandler.get(); + SetStateHandler(std::move(newHandler)); + UpdateRichPresence({}); + } + + switch (reason) { + case Reason::IncompatibleVersion: mainMenu->SwitchToSection(_("\f[c:0x704a4a]Cannot connect to the server!\f[c]\n\n\nYour client version is not compatible with the server.")); break; + case Reason::ServerIsFull: mainMenu->SwitchToSection(_("\f[c:0x704a4a]Cannot connect to the server!\f[c]\n\n\nServer capacity is full.\nPlease try it later.")); break; + case Reason::ServerNotReady: mainMenu->SwitchToSection(_("\f[c:0x704a4a]Cannot connect to the server!\f[c]\n\n\nServer is not in a state where it can process your request.\nPlease try again in a few seconds.")); break; + case Reason::ServerStopped: mainMenu->SwitchToSection(_("\f[c:0x704a4a]Connection has been closed!\f[c]\n\n\nServer is shutting down.\nPlease try it later.")); break; + case Reason::ConnectionLost: mainMenu->SwitchToSection(_("\f[c:0x704a4a]Connection has been lost!\f[c]\n\n\nPlease try it again and if the problem persists,\ncheck your network connection.")); break; + case Reason::ConnectionTimedOut: mainMenu->SwitchToSection(_("\f[c:0x704a4a]Cannot connect to the server!\f[c]\n\n\nThe server is not responding for connection request.")); break; + case Reason::Kicked: mainMenu->SwitchToSection(_("\f[c:0x704a4a]Connection has been closed!\f[c]\n\n\nYou have been \f[c:0x907050]kicked\f[c] off the server.\nContact server administrators for more information.")); break; + case Reason::Banned: mainMenu->SwitchToSection(_("\f[c:0x704a4a]Connection has been closed!\f[c]\n\n\nYou have been \f[c:0x725040]banned\f[c] off the server.\nContact server administrators for more information.")); break; + } + }); } } void GameEventHandler::OnPacketReceived(const Peer& peer, std::uint8_t channelId, std::uint8_t* data, std::size_t dataLength) { - if (auto multiLevelHandler = dynamic_cast(_currentHandler.get())) { - if (multiLevelHandler->OnPacketReceived(peer, channelId, data, dataLength)) { - return; - } - } - - if (_networkManager->GetState() == NetworkState::Listening) { + bool isServer = (_networkManager->GetState() == NetworkState::Listening); + if (isServer) { auto packetType = (ClientPacketType)data[0]; switch (packetType) { case ClientPacketType::Ping: { @@ -606,15 +625,16 @@ void GameEventHandler::OnPacketReceived(const Peer& peer, std::uint8_t channelId _networkManager->SendToPeer(peer, NetworkChannel::Main, data, sizeof(data)); break; } - case ClientPacketType::Auth: { + /*case ClientPacketType::Auth: { // TODO: Move this to MultiLevelHandler std::uint8_t flags = 0; if (PreferencesCache::EnableReforged) { flags |= 0x01; } - String episodeName = "unknown"_s; - String levelName = "race3"_s; + // TODO: Hardcoded level + String episodeName = "prince"_s; + String levelName = "01_castle1"_s; MemoryStream packet(10 + episodeName.size() + levelName.size()); packet.WriteValue((std::uint8_t)ServerPacketType::LoadLevel); @@ -626,7 +646,7 @@ void GameEventHandler::OnPacketReceived(const Peer& peer, std::uint8_t channelId _networkManager->SendToPeer(peer, NetworkChannel::Main, packet.GetBuffer(), packet.GetSize()); break; - } + }*/ } } else { @@ -653,6 +673,17 @@ void GameEventHandler::OnPacketReceived(const Peer& peer, std::uint8_t channelId } } } + + if (auto multiLevelHandler = dynamic_cast(_currentHandler.get())) { + if (multiLevelHandler->OnPacketReceived(peer, channelId, data, dataLength)) { + return; + } + } + + if (isServer && (ClientPacketType)data[0] == ClientPacketType::Auth) { + // Message was not processed by level handler, kick the client + _networkManager->KickClient(peer, Reason::ServerNotReady); + } } #endif diff --git a/Sources/Shared/Containers/Array.h b/Sources/Shared/Containers/Array.h index 6ec0de55..9a551e9f 100644 --- a/Sources/Shared/Containers/Array.h +++ b/Sources/Shared/Containers/Array.h @@ -39,7 +39,7 @@ namespace Death::Containers namespace Implementation { template inline void construct(T& value, First&& first, Next&& ...next) { - new(&value) T { std::forward(first), std::forward(next)... }; + new(&value) T{std::forward(first), std::forward(next)...}; } template inline void construct(T& value) { new(&value) T(); @@ -51,15 +51,6 @@ namespace Death::Containers } #endif - template struct DefaultDeleter { - T operator()() const { - return T {}; - } - }; - template struct DefaultDeleter { - void(*operator()() const)(T*, std::size_t) { return nullptr; } - }; - template struct CallDeleter { void operator()(D deleter, T* data, std::size_t size) const { deleter(data, size); @@ -105,181 +96,425 @@ namespace Death::Containers template class Array { public: + /** @brief Element type */ typedef T Type; - typedef D Deleter; - - template::value>::type> /*implicit*/ Array(U) noexcept : - _data{nullptr}, _size{0}, _deleter(Implementation::DefaultDeleter{}()) {} - /*implicit*/ Array() noexcept : _data(nullptr), _size(0), _deleter(Implementation::DefaultDeleter{}()) {} + /** + * @brief Deleter type + * + * Defaults to pointer to a @cpp void(T*, std::size_t) @ce function, + * where first is array pointer and second array size. + */ + typedef D Deleter; + /** + * @brief Default constructor + * + * Creates a zero-sized array. Move an @ref Array with a nonzero size + * onto the instance to make it useful. + */ + template::value>::type> /*implicit*/ Array(U) noexcept : _data{nullptr}, _size{0}, _deleter{} {} + + /*implicit*/ Array() noexcept : _data(nullptr), _size(0), _deleter{} {} + + /** + * @brief Construct a default-initialized array + * + * Creates an array of given size, the contents are default-initialized + * (i.e. trivial types are not initialized, default constructor called + * otherwise). If the size is zero, no allocation is done. Because of + * the differing behavior for trivial types it's better to explicitly + * use either the @ref Array(ValueInitT, std::size_t) or the + * @ref Array(NoInitT, std::size_t) variant instead. + */ explicit Array(DefaultInitT, std::size_t size) : _data{size ? new T[size] : nullptr}, _size{size}, _deleter{nullptr} {} + /** + * @brief Construct a value-initialized array + * + * Creates an array of given size, the contents are value-initialized + * (i.e. trivial types are zero-initialized, default constructor called + * otherwise). This is the same as @ref Array(std::size_t). If the size + * is zero, no allocation is done. + */ explicit Array(ValueInitT, std::size_t size) : _data{size ? new T[size]() : nullptr}, _size{size}, _deleter{nullptr} {} + /** + * @brief Construct an array without initializing its contents + * + * Creates an array of given size, the contents are *not* initialized. + * If the size is zero, no allocation is done. Useful if you will be + * overwriting all elements later anyway or if you need to call custom + * constructors in a way that's not expressible via any other + * @ref Array constructor. + * + * For trivial types is equivalent to @ref Array(DefaultInitT, std::size_t), + * with @ref deleter() being the default (@cpp nullptr @ce) as well. + * For non-trivial types, the data are allocated as a @cpp char @ce + * array. Destruction is done using a custom deleter that explicitly + * calls the destructor on *all elements* and then deallocates the data + * as a @cpp char @ce array again --- which means that for non-trivial + * types you're expected to construct all elements using placement new + * (or for example @ref std::uninitialized_copy()) in order to avoid + * calling destructors on uninitialized memory. + */ explicit Array(NoInitT, std::size_t size) : _data{size ? Implementation::noInitAllocate(size) : nullptr}, _size{size}, _deleter{Implementation::noInitDeleter()} {} - template explicit Array(DirectInitT, std::size_t size, Args&&... args); - - explicit Array(InPlaceInitT, std::initializer_list list); - + /** + * @brief Construct a direct-initialized array + * + * Allocates the array using the @ref Array(NoInitT, std::size_t) + * constructor and then initializes each element with placement new + * using forwarded @p args. + */ + template explicit Array(DirectInitT, std::size_t size, Args&&... args); + + /** + * @brief Construct a list-initialized array + * + * Allocates the array using the @ref Array(NoInitT, std::size_t) + * constructor and then copy-initializes each element with placement + * new using values from @p list. To save typing you can also use the + * @ref array(ArrayView) / + * @ref array(std::initializer_list) shorthands. + */ + /*implicit*/ Array(InPlaceInitT, ArrayView list); + + /** @overload */ + /*implicit*/ Array(InPlaceInitT, std::initializer_list list); + + /** + * @brief Construct a value-initialized array + * + * Alias to @ref Array(ValueInitT, std::size_t). + */ explicit Array(std::size_t size) : Array{ValueInit, size} {} - explicit Array(T* data, std::size_t size, D deleter = Implementation::DefaultDeleter{}()) : _data{data}, _size{size}, _deleter(deleter) {} - - ~Array() { - Implementation::CallDeleter{}(_deleter, _data, _size); - } - + /** + * @brief Wrap an existing array with an explicit deleter + * + * The @p deleter will be *unconditionally* called on destruction with + * @p data and @p size as an argument. In particular, it will be also + * called if @p data is @cpp nullptr @ce or @p size is @cpp 0 @ce. + * + * In case of a moved-out instance, the deleter gets reset to a + * default-constructed value alongside the array pointer and size. For + * plain deleter function pointers it effectively means + * @cpp delete[] nullptr @ce gets called when destructing a moved-out + * instance (which is a no-op), for stateful deleters you have to + * ensure the deleter similarly does nothing in its default state. + */ + explicit Array(T* data, std::size_t size, D deleter = {}) noexcept : _data{data}, _size{size}, _deleter(deleter) {} + + /** + * @brief Wrap an existing array view with an explicit deleter + * + * Convenience overload of @ref Array(T*, std::size_t, D) for cases + * where the pointer and size is already wrapped in an @ref ArrayView, + * such as when creating non-owned @ref Array instances. + */ + explicit Array(ArrayView view, D deleter) noexcept : Array{view.data(), view.size(), deleter} {} + + /** @brief Copying is not allowed */ Array(const Array&) = delete; + /** + * @brief Move constructor + * + * Resets data pointer, size and deleter of @p other to be equivalent + * to a default-constructed instance. + */ Array(Array&& other) noexcept; + /** + * @brief Destructor + * + * Calls @ref deleter() on the owned @ref data(). + */ + ~Array() { Implementation::CallDeleter{}(_deleter, _data, _size); } + + /** @brief Copying is not allowed */ Array& operator=(const Array&) = delete; + /** + * @brief Move assignment + * + * Swaps data pointer, size and deleter of the two instances. + */ Array& operator=(Array&& other) noexcept; + /** @brief Convert to external view representation */ template::to(std::declval>()))> /*implicit*/ operator U() { return Implementation::ArrayViewConverter::to(*this); } + /** @overload */ template::to(std::declval>()))> constexpr /*implicit*/ operator U() const { return Implementation::ArrayViewConverter::to(*this); } #if !defined(DEATH_MSVC2019_COMPATIBILITY) + /** @brief Whether the array is non-empty */ explicit operator bool() const { return _data; } #endif - /*implicit*/ operator T* ()& { - return _data; - } - - /*implicit*/ operator const T* () const& { - return _data; - } - - T* data() { - return _data; - } - const T* data() const { - return _data; - } - - D deleter() const { - return _deleter; - } - - std::size_t size() const { - return _size; - } - - bool empty() const { - return !_size; - } - - T* begin() { - return _data; - } - const T* begin() const { - return _data; - } - const T* cbegin() const { - return _data; - } - - T* end() { - return _data + _size; - } - const T* end() const { - return _data + _size; - } - const T* cend() const { - return _data + _size; - } - + /** @brief Conversion to array type */ + /*implicit*/ operator T*() & { return _data; } + + /** @overload */ + /*implicit*/ operator const T*() const & { return _data; } + + /** @brief Array data */ + T* data() { return _data; } + const T* data() const { return _data; } /**< @overload */ + + /** + * @brief Array deleter + * + * If set to @cpp nullptr @ce, the contents are deleted using standard + * @cpp operator delete[] @ce. + * @see @ref Array(T*, std::size_t, D) + */ + D deleter() const { return _deleter; } + + /** + * @brief Array size + * + * @see @ref isEmpty() + */ + std::size_t size() const { return _size; } + + /** + * @brief Whether the array is empty + * @m_since_latest + * + * @see @ref size() + */ + bool empty() const { return !_size; } + + /** + * @brief Pointer to first element + * + * @see @ref front(), @ref operator[]() + */ + T* begin() { return _data; } + const T* begin() const { return _data; } /**< @overload */ + const T* cbegin() const { return _data; } /**< @overload */ + + /** + * @brief Pointer to (one item after) last element + * + * @see @ref back(), @ref operator[]() + */ + T* end() { return _data+_size; } + const T* end() const { return _data+_size; } /**< @overload */ + const T* cend() const { return _data+_size; } /**< @overload */ + + /** + * @brief First element + * + * Expects there is at least one element. + * @see @ref begin(), @ref operator[]() + */ T& front(); - const T& front() const; - + const T& front() const; /**< @overload */ + + /** + * @brief Last element + * + * Expects there is at least one element. + * @see @ref end(), @ref operator[]() + */ T& back(); - const T& back() const; - + const T& back() const; /**< @overload */ + + // Has to be done this way because otherwise it causes ambiguity with a builtin operator[] for pointers if an int + // or ssize_t is used due to the implicit pointer conversion. Sigh. + template::value>::type> T& operator[](U i); + template::value>::type> const T& operator[](U i) const; + + /** + * @brief View on a slice + * + * Equivalent to @ref ArrayView::slice(T*, T*) const and overloads. + */ ArrayView slice(T* begin, T* end) { return ArrayView(*this).slice(begin, end); } + /** @overload */ ArrayView slice(const T* begin, const T* end) const { return ArrayView(*this).slice(begin, end); } + /** @overload */ ArrayView slice(std::size_t begin, std::size_t end) { return ArrayView(*this).slice(begin, end); } + /** @overload */ ArrayView slice(std::size_t begin, std::size_t end) const { return ArrayView(*this).slice(begin, end); } - template StaticArrayView slice(T* begin) { - return ArrayView(*this).template slice(begin); + /** + * @brief View on a slice of given size + * @m_since_latest + * + * Equivalent to @ref ArrayView::sliceSize(T*, std::size_t) const and + * overloads. + */ + template::value && !std::is_convertible::value>::type> ArrayView sliceSize(U begin, std::size_t size) { + return ArrayView{*this}.sliceSize(begin, size); + } + /** @overload */ + template::value && !std::is_convertible::value>::type> ArrayView sliceSize(const U begin, std::size_t size) const { + return ArrayView{*this}.sliceSize(begin, size); + } + /** @overload */ + ArrayView sliceSize(std::size_t begin, std::size_t size) { + return ArrayView{*this}.sliceSize(begin, size); + } + /** @overload */ + ArrayView sliceSize(std::size_t begin, std::size_t size) const { + return ArrayView{*this}.sliceSize(begin, size); + } + + /** + * @brief Fixed-size view on a slice + * + * Equivalent to @ref ArrayView::slice(T*) const and overloads. + */ + template::value && !std::is_convertible::value>::type> StaticArrayView slice(U begin) { + return ArrayView(*this).template slice(begin); } - template StaticArrayView slice(const T* begin) const { - return ArrayView(*this).template slice(begin); + /** @overload */ + template::value && !std::is_convertible::value>::type> StaticArrayView slice(U begin) const { + return ArrayView(*this).template slice(begin); } - template StaticArrayView slice(std::size_t begin) { - return ArrayView(*this).template slice(begin); + /** @overload */ + template StaticArrayView slice(std::size_t begin) { + return ArrayView(*this).template slice(begin); } - template StaticArrayView slice(std::size_t begin) const { - return ArrayView(*this).template slice(begin); + /** @overload */ + template StaticArrayView slice(std::size_t begin) const { + return ArrayView(*this).template slice(begin); } + /** + * @brief Fixed-size view on a slice + * + * Equivalent to @ref ArrayView::slice() const. + */ template StaticArrayView slice() { return ArrayView(*this).template slice(); } - + /** @overload */ template StaticArrayView slice() const { return ArrayView(*this).template slice(); } - ArrayView prefix(T* end) { + /** + * @brief View on a prefix until a pointer + * + * Equivalent to @ref ArrayView::prefix(T*) const. + */ + template::value && !std::is_convertible::value>::type> + ArrayView prefix(U end) { return ArrayView(*this).prefix(end); } - ArrayView prefix(const T* end) const { - return ArrayView(*this).prefix(end); - } - ArrayView prefix(std::size_t end) { - return ArrayView(*this).prefix(end); - } - ArrayView prefix(std::size_t end) const { + /** @overload */ + template::value && !std::is_convertible::value>::type> + ArrayView prefix(U end) const { return ArrayView(*this).prefix(end); } - - template StaticArrayView prefix() { - return ArrayView(*this).template prefix(); - } - template StaticArrayView prefix() const { - return ArrayView(*this).template prefix(); - } - + + /** + * @brief View on a suffix after a pointer + * + * Equivalent to @ref ArrayView::suffix(T*) const. + */ ArrayView suffix(T* begin) { return ArrayView(*this).suffix(begin); } + /** @overload */ ArrayView suffix(const T* begin) const { return ArrayView(*this).suffix(begin); } - ArrayView suffix(std::size_t begin) { - return ArrayView(*this).suffix(begin); - } - ArrayView suffix(std::size_t begin) const { - return ArrayView(*this).suffix(begin); - } - ArrayView except(std::size_t count) { - return ArrayView(*this).except(count); + /** + * @brief View on the first @p size items + * + * Equivalent to @ref ArrayView::prefix(std::size_t) const. + */ + ArrayView prefix(std::size_t end) { + return ArrayView(*this).prefix(end); } - ArrayView except(std::size_t count) const { - return ArrayView(*this).except(count); + /** @overload */ + ArrayView prefix(std::size_t end) const { + return ArrayView(*this).prefix(end); } + /** + * @brief Fixed-size view on the first @p size_ items + * + * Equivalent to @ref ArrayView::prefix() const. + */ + template StaticArrayView prefix() { + return ArrayView(*this).template prefix(); + } + /** @overload */ + template StaticArrayView prefix() const { + return ArrayView(*this).template prefix(); + } + + /** + * @brief Fixed-size view on the last @p size_ items + * + * Equivalent to @ref ArrayView::suffix() const. + */ + template StaticArrayView suffix() { + return ArrayView(*this).template suffix(); + } + /** @overload */ + template StaticArrayView suffix() const { + return ArrayView(*this).template suffix(); + } + + /** + * @brief View except the first @p size_ items + * + * Equivalent to @ref ArrayView::exceptPrefix(std::size_t) const. + */ + ArrayView exceptPrefix(std::size_t size_) { + return ArrayView(*this).exceptPrefix(size_); + } + /** @overload */ + ArrayView exceptPrefix(std::size_t size_) const { + return ArrayView(*this).exceptPrefix(size_); + } + + /** + * @brief View except the last @p size items + * + * Equivalent to @ref ArrayView::exceptSuffix(). + */ + ArrayView exceptSuffix(std::size_t size) { + return ArrayView(*this).exceptSuffix(size); + } + /** @overload */ + ArrayView exceptSuffix(std::size_t size) const { + return ArrayView(*this).exceptSuffix(size); + } + + /** + * @brief Release data storage + * + * Returns the data pointer and resets data pointer, size and deleter + * to be equivalent to a default-constructed instance. Deleting the + * returned array is user responsibility --- note the array might have + * a custom @ref deleter() and so @cpp delete[] @ce might not be always + * appropriate. + */ T* release(); private: @@ -288,26 +523,57 @@ namespace Death::Containers D _deleter; }; + /** + @brief Construct a list-initialized array + + Convenience shortcut to the @ref Array::Array(InPlaceInitT, ArrayView) + constructor. Not present as an implicit constructor in order to avoid the same + usability issues as with @ref std::vector. + */ + template inline Array array(ArrayView list) { + return Array{InPlaceInit, list}; + } + + /** + @brief Construct a list-initialized array + + Convenience shortcut to the @ref Array::Array(InPlaceInitT, std::initializer_list) + constructor. Not present as an implicit constructor in order to avoid the same + usability issues as with @ref std::vector. + */ template inline Array array(std::initializer_list list) { return Array{InPlaceInit, list}; } + /** + @brief Make a view on an @ref Array + + Convenience alternative to converting to an @ref ArrayView explicitly. + */ template inline ArrayView arrayView(Array& array) { return ArrayView{array}; } + /** + @brief Make a view on a const @ref Array + + Convenience alternative to converting to an @ref ArrayView explicitly. + */ template inline ArrayView arrayView(const Array& array) { return ArrayView{array}; } + /** @brief Reinterpret-cast an array */ template inline ArrayView arrayCast(Array& array) { return arrayCast(arrayView(array)); } + /** @overload */ template inline ArrayView arrayCast(const Array& array) { return arrayCast(arrayView(array)); } + /** @brief Array size */ template std::size_t arraySize(const Array& view) { return view.size(); } @@ -315,7 +581,7 @@ namespace Death::Containers template inline Array::Array(Array&& other) noexcept : _data{other._data}, _size{other._size}, _deleter{other._deleter} { other._data = nullptr; other._size = 0; - other._deleter = D {}; + other._deleter = D{}; } template template Array::Array(DirectInitT, std::size_t size, Args&&... args) : Array{NoInit, size} { @@ -323,11 +589,19 @@ namespace Death::Containers Implementation::construct(_data[i], std::forward(args)...); } - template Array::Array(InPlaceInitT, std::initializer_list list) : Array{NoInit, list.size()} { + template Array::Array(InPlaceInitT, const ArrayView list) : Array{NoInit, list.size()} { std::size_t i = 0; - for (const T& item : list) new(_data + i++) T { item }; + for (const T& item : list) + // Can't use {}, see the GCC 4.8-specific overload for details +#if defined(DEATH_TARGET_GCC) && !defined(DEATH_TARGET_CLANG) && __GNUC__ < 5 + Implementation::construct(_data[i++], item); +#else + new(_data + i++) T{item}; +#endif } + template Array::Array(InPlaceInitT, std::initializer_list list) : Array{InPlaceInit, arrayView(list)} { } + template inline Array& Array::operator=(Array&& other) noexcept { using std::swap; swap(_data, other._data); @@ -336,6 +610,10 @@ namespace Death::Containers return *this; } + template template const T& Array::operator[](const U i) const { + return _data[i]; + } + template const T& Array::front() const { DEATH_ASSERT(_size != 0, _data[0], "Containers::Array::front(): Array is empty"); return _data[0]; @@ -346,6 +624,10 @@ namespace Death::Containers return _data[_size - 1]; } + template template T& Array::operator[](const U i) { + return const_cast(static_cast&>(*this)[i]); + } + template T& Array::front() { return const_cast(static_cast&>(*this).front()); } @@ -358,7 +640,7 @@ namespace Death::Containers T* const data = _data; _data = nullptr; _size = 0; - _deleter = D {}; + _deleter = D{}; return data; } diff --git a/Sources/Shared/Containers/ArrayView.h b/Sources/Shared/Containers/ArrayView.h index 7579ea67..4aff8342 100644 --- a/Sources/Shared/Containers/ArrayView.h +++ b/Sources/Shared/Containers/ArrayView.h @@ -52,108 +52,231 @@ namespace Death::Containers template class ArrayView { public: + /** @brief Element type */ typedef T Type; - /* To avoid ambiguity in certain cases of passing 0 to overloads that take either a ArrayView or std::size_t */ + /** + * @brief Default constructor + * + * Creates an empty @cpp nullptr @ce view. Copy a non-empty @ref Array + * or @ref ArrayView onto the instance to make it useful. + */ template::value>::type> constexpr /*implicit*/ ArrayView(U) noexcept : _data{}, _size{} {} constexpr /*implicit*/ ArrayView() noexcept : _data{}, _size{} {} + /** + * @brief Construct a view on an array with explicit size + * @param data Data pointer + * @param size Data size + * + * @see @ref arrayView(T*, std::size_t) + */ constexpr /*implicit*/ ArrayView(T* data, std::size_t size) noexcept : _data(data), _size(size) {} + /** + * @brief Construct a view on a fixed-size array + * @param data Fixed-size array + * + * Enabled only if @cpp T* @ce is implicitly convertible to @cpp U* @ce. + * Expects that both types have the same size. + */ template::value>::type> constexpr /*implicit*/ ArrayView(U(&data)[size]) noexcept : _data{data}, _size{size} { static_assert(sizeof(T) == sizeof(U), "Type sizes are not compatible"); } + /** + * @brief Construct a view on an @ref ArrayView + * + * Enabled only if @cpp T* @ce is implicitly convertible to @cpp U* @ce. + * Expects that both types have the same size. + */ template::value>::type> constexpr /*implicit*/ ArrayView(ArrayView view) noexcept : _data{view}, _size{view.size()} { static_assert(sizeof(T) == sizeof(U), "Type sizes are not compatible"); } + /** + * @brief Construct a view on a @ref StaticArrayView + * + * Enabled only if @cpp T* @ce is implicitly convertible to @cpp U* @ce. + * Expects that both types have the same size. + */ template::value>::type> constexpr /*implicit*/ ArrayView(StaticArrayView view) noexcept : _data{view}, _size{size} { static_assert(sizeof(U) == sizeof(T), "Type sizes are not compatible"); } + /** @brief Construct a view on an external type / from an external representation */ template::type>::from(std::declval()))> constexpr /*implicit*/ ArrayView(U&& other) noexcept : ArrayView{Implementation::ArrayViewConverter::type>::from(std::forward(other))} {} - + + /** @brief Convert the view to external representation */ template::to(std::declval>()))> constexpr /*implicit*/ operator U() const { return Implementation::ArrayViewConverter::to(*this); } #if !defined(DEATH_MSVC2019_COMPATIBILITY) - constexpr explicit operator bool() const { - return _data; - } + /** @brief Whether the view is non-empty */ + constexpr explicit operator bool() const { return _data; } #endif - constexpr /*implicit*/ operator T* () const { - return _data; - } - - constexpr T* data() const { - return _data; - } - - constexpr std::size_t size() const { - return _size; - } - - constexpr bool empty() const { - return !_size; - } - - constexpr T* begin() const { - return _data; - } - constexpr T* cbegin() const { - return _data; - } - - constexpr T* end() const { - return _data + _size; - } - constexpr T* cend() const { - return _data + _size; - } - - T& front() const; - - T& back() const; - + /** @brief Conversion to the underlying type */ + constexpr /*implicit*/ operator T*() const { return _data; } + + /** @brief View data */ + constexpr T* data() const { return _data; } + + /** @brief View size */ + constexpr std::size_t size() const { return _size; } + + /** @brief Whether the view is empty */ + constexpr bool empty() const { return !_size; } + + /** @brief Pointer to the first element */ + constexpr T* begin() const { return _data; } + /** @overload */ + constexpr T* cbegin() const { return _data; } + + /** @brief Pointer to (one item after) the last element */ + constexpr T* end() const { return _data + _size; } + /** @overload */ + constexpr T* cend() const { return _data + _size; } + + /** + * @brief First element + * + * Expects there is at least one element. + */ + constexpr T& front() const; + + /** + * @brief Last element + * + * Expects there is at least one element. + */ + constexpr T& back() const; + + /** + * @brief Element access + * + * Expects that @p i is less than @ref size(). + */ + template::value>::type> constexpr T& operator[](U i) const; + + /** + * @brief View slice + * + * Both arguments are expected to be in range. + */ constexpr ArrayView slice(T* begin, T* end) const; - + /** @overload */ constexpr ArrayView slice(std::size_t begin, std::size_t end) const; - template constexpr StaticArrayView slice(T* begin) const; - + /** + * @brief View slice of given size + * + * Equivalent to @cpp data.slice(begin, begin + size) @ce. + */ + template::value && !std::is_convertible::value>::type> constexpr ArrayView sliceSize(U begin, std::size_t size) const { + return slice(begin, begin + size); + } + /** @overload */ + constexpr ArrayView sliceSize(std::size_t begin, std::size_t size) const { + return slice(begin, begin + size); + } + + /** + * @brief Fixed-size view slice + * + * Both @p begin and @cpp begin + size_ @ce are expected to be in + * range. + */ + template::value && !std::is_convertible::value>::type> constexpr StaticArrayView slice(U begin) const; + /** @overload */ template constexpr StaticArrayView slice(std::size_t begin) const; + /** + * @brief Fixed-size view slice + * + * At compile time expects that @cpp begin < end_ @ce, at runtime that + * @p end_ is not larger than @ref size(). + */ template constexpr StaticArrayView slice() const; - constexpr ArrayView prefix(T* end) const { - return end ? slice(_data, end) : nullptr; + /** + * @brief Fixed-size view slice of given size + * + * Expects that `begin_ + size_` is not larger than @ref size(). + */ + template constexpr StaticArrayView sliceSize() const { + return slice(); + } + + /** + * @brief View prefix until a pointer + * + * Equivalent to @cpp data.slice(data.begin(), end) @ce. If @p end is + * @cpp nullptr @ce, returns zero-sized @cpp nullptr @ce array. + */ + template::value && !std::is_convertible::value>::type> constexpr ArrayView prefix(U end) const { + return static_cast(end) ? slice(_data, end) : nullptr; + } + + /** + * @brief View suffix after a pointer + * + * Equivalent to @cpp data.slice(begin, data.end()) @ce. If @p begin is + * @cpp nullptr @ce and the original array isn't, returns zero-sized + * @cpp nullptr @ce array. + */ + constexpr ArrayView suffix(T* begin) const { + return _data && !begin ? nullptr : slice(begin, _data + _size); } - constexpr ArrayView prefix(std::size_t end) const { - return slice(0, end); + /** + * @brief View on the first @p size items + * + * Equivalent to @cpp data.slice(0, size) @ce. + */ + constexpr ArrayView prefix(std::size_t size) const { + return slice(0, size); } - template constexpr StaticArrayView prefix() const { - return slice<0, end_>(); + /** + * @brief Fixed-size view on the first @p size_ items + * + * Equivalent to @cpp data.slice<0, size_>() @ce. + */ + template constexpr StaticArrayView prefix() const { + return slice<0, size_>(); } - constexpr ArrayView suffix(T* begin) const { - return _data && !begin ? nullptr : slice(begin, _data + _size); + /** + * @brief Fixed-size view on the last @p size_ items + * + * Equivalent to @cpp data.slice(data.size() - size_) @ce. + */ + template constexpr StaticArrayView suffix() const { + return slice(_size - size_); } - constexpr ArrayView suffix(std::size_t begin) const { - return slice(begin, _size); + /** + * @brief View except the first @p size items + * + * Equivalent to @cpp data.slice(size, data.size()) @ce. + */ + constexpr ArrayView exceptPrefix(std::size_t size) const { + return slice(size, _size); } - constexpr ArrayView except(std::size_t count) const { - return slice(0, _size - count); + /** + * @brief View except the last @p size items + * + * Equivalent to @cpp data.slice(0, data.size() - size) @ce. + */ + constexpr ArrayView exceptSuffix(std::size_t size) const { + return slice(0, _size - size); } private: @@ -173,52 +296,74 @@ namespace Death::Containers template<> class ArrayView { public: + /** @brief Element type */ typedef void Type; - /* To avoid ambiguity in certain cases of passing 0 to overloads that take either a ArrayView or std::size_t */ + /** + * @brief Default constructor + * + * Creates an empty @cpp nullptr @ce view. Copy a non-empty @ref Array + * or @ref ArrayView onto the instance to make it useful. + */ template::value>::type> constexpr /*implicit*/ ArrayView(U) noexcept : _data{}, _size{} {} constexpr /*implicit*/ ArrayView() noexcept : _data{}, _size{} {} + /** + * @brief Construct a view on a type-erased array with explicit length + * @param data Data pointer + * @param size Data size in bytes + */ constexpr /*implicit*/ ArrayView(void* data, std::size_t size) noexcept : _data(data), _size(size) {} + /** + * @brief Construct a view on a typed array with explicit length + * @param data Data pointer + * @param size Data size + * + * Size is recalculated to size in bytes. + */ template constexpr /*implicit*/ ArrayView(T* data, std::size_t size) noexcept : _data(data), _size(size * sizeof(T)) {} + /** + * @brief Construct a view on a fixed-size array + * @param data Fixed-size array + * + * Size in bytes is calculated automatically. + */ template::value>::type > constexpr /*implicit*/ ArrayView(T(&data)[size]) noexcept : _data(data), _size(size * sizeof(T)) {} + /** @brief Construct a void view on any @ref ArrayView */ template::value>::type > constexpr /*implicit*/ ArrayView(ArrayView array) noexcept : _data(array), _size(array.size() * sizeof(T)) {} + /** @brief Construct a void view on any @ref StaticArrayView */ template::value>::type > constexpr /*implicit*/ ArrayView(const StaticArrayView& array) noexcept : _data{array}, _size{size * sizeof(T)} {} + /** @brief Construct a view on an external type / from an external representation */ template::type>::from(std::declval()))> constexpr /*implicit*/ ArrayView(T&& other) noexcept : ArrayView{Implementation::ErasedArrayViewConverter::type>::from(other)} {} #if !defined(DEATH_MSVC2019_COMPATIBILITY) - constexpr explicit operator bool() const { - return _data; - } + /** @brief Whether the view is non-empty */ + constexpr explicit operator bool() const { return _data; } #endif - constexpr /*implicit*/ operator void* () const { - return _data; - } + /** @brief Conversion to the underlying type */ + constexpr /*implicit*/ operator void*() const { return _data; } - constexpr void* data() const { - return _data; - } + /** @brief View data */ + constexpr void* data() const { return _data; } - constexpr std::size_t size() const { - return _size; - } + /** @brief View size */ + constexpr std::size_t size() const { return _size; } - constexpr bool empty() const { - return !_size; - } + /** @brief Whether the view is empty */ + constexpr bool empty() const { return !_size; } private: void* _data; @@ -237,78 +382,136 @@ namespace Death::Containers template<> class ArrayView { public: + /** @brief Element type */ typedef const void Type; - /* To avoid ambiguity in certain cases of passing 0 to overloads that take either a ArrayView or std::size_t */ + /** + * @brief Default constructor + * + * Creates an empty @cpp nullptr @ce view. Copy a non-empty @ref Array + * or @ref ArrayView onto the instance to make it useful. + */ template::value>::type> constexpr /*implicit*/ ArrayView(U) noexcept : _data{}, _size{} {} constexpr /*implicit*/ ArrayView() noexcept : _data{}, _size{} {} + /** + * @brief Construct a view on a type-erased array array with explicit length + * @param data Data pointer + * @param size Data size in bytes + */ constexpr /*implicit*/ ArrayView(const void* data, std::size_t size) noexcept : _data(data), _size(size) {} + /** + * @brief Constructor a view on a typed array with explicit length + * @param data Data pointer + * @param size Data size + * + * Size is recalculated to size in bytes. + */ template constexpr /*implicit*/ ArrayView(const T* data, std::size_t size) noexcept : _data(data), _size(size * sizeof(T)) {} + /** + * @brief Construct a view on a fixed-size array + * @param data Fixed-size array + * + * Size in bytes is calculated automatically. + */ template constexpr /*implicit*/ ArrayView(T(&data)[size]) noexcept : _data(data), _size(size * sizeof(T)) {} + /** @brief Construct a const void view on an @ref ArrayView */ constexpr /*implicit*/ ArrayView(ArrayView array) noexcept : _data{array}, _size{array.size()} {} - + + /** @brief Construct a const void view on any @ref ArrayView */ template constexpr /*implicit*/ ArrayView(ArrayView array) noexcept : _data(array), _size(array.size() * sizeof(T)) {} + /** @brief Construct a const void view on any @ref StaticArrayView */ template constexpr /*implicit*/ ArrayView(const StaticArrayView& array) noexcept : _data{array}, _size{size * sizeof(T)} {} + /** @brief Construct a view on an external type / from an external representation */ template::from(std::declval()))> constexpr /*implicit*/ ArrayView(const T& other) noexcept : ArrayView{Implementation::ErasedArrayViewConverter::from(other)} {} #if !defined(DEATH_MSVC2019_COMPATIBILITY) - constexpr explicit operator bool() const { - return _data; - } + /** @brief Whether the view is non-empty */ + constexpr explicit operator bool() const { return _data; } #endif - constexpr /*implicit*/ operator const void* () const { - return _data; - } + /** @brief Conversion to the underlying type */ + constexpr /*implicit*/ operator const void*() const { return _data; } - constexpr const void* data() const { - return _data; - } + /** @brief View data */ + constexpr const void* data() const { return _data; } - constexpr std::size_t size() const { - return _size; - } + /** @brief View size */ + constexpr std::size_t size() const { return _size; } - constexpr bool empty() const { - return !_size; - } + /** @brief Whether the view is empty */ + constexpr bool empty() const { return !_size; } private: const void* _data; std::size_t _size; }; + /** + @brief Make a view on an array of specific length + + Convenience alternative to @ref ArrayView::ArrayView(T*, std::size_t). + */ template constexpr ArrayView arrayView(T* data, std::size_t size) { return ArrayView{data, size}; } + /** + @brief Make a view on fixed-size array + + Convenience alternative to @ref ArrayView::ArrayView(U(&)[size]). + */ template constexpr ArrayView arrayView(T(&data)[size]) { return ArrayView{data}; } + /** + @brief Make a view on an initializer list + + Not present as a constructor in order to avoid accidental dangling references + with r-value initializer lists. + */ template ArrayView arrayView(std::initializer_list list) { return ArrayView{list.begin(), list.size()}; } + /** + @brief Make a view on @ref StaticArrayView + + Convenience alternative to @ref ArrayView::ArrayView(StaticArrayView). + */ template constexpr ArrayView arrayView(StaticArrayView view) { return ArrayView{view}; } + /** + @brief Make a view on a view + + Equivalent to the implicit @ref ArrayView copy constructor --- it shouldn't be + an error to call @ref arrayView() on itself. + */ template constexpr ArrayView arrayView(ArrayView view) { return view; } + /** @brief Make a view on an external type / from an external representation */ template::type>::from(std::declval()))> constexpr U arrayView(T&& other) { return Implementation::ErasedArrayViewConverter::type>::from(std::forward(other)); } + /** + @brief Reinterpret-cast an array view + + Size of the new array is calculated as @cpp view.size()*sizeof(T)/sizeof(U) @ce. + Expects that both types are [standard layout](http://en.cppreference.com/w/cpp/concept/StandardLayoutType) + and the total byte size doesn't change. + */ template ArrayView arrayCast(ArrayView view) { static_assert(std::is_standard_layout::value, "The source type is not standard layout"); static_assert(std::is_standard_layout::value, "The target type is not standard layout"); @@ -318,6 +521,13 @@ namespace Death::Containers return { reinterpret_cast(view.begin()), size }; } + /** + @brief Reinterpret-cast a void array view + + Size of the new array is calculated as @cpp view.size()/sizeof(U) @ce. + Expects that the target type is [standard layout](http://en.cppreference.com/w/cpp/concept/StandardLayoutType) + and the total byte size doesn't change. + */ template ArrayView arrayCast(ArrayView view) { static_assert(std::is_standard_layout::value, "The target type is not standard layout"); const std::size_t size = view.size() / sizeof(U); @@ -326,19 +536,23 @@ namespace Death::Containers return { reinterpret_cast(view.data()), size }; } + /** @overload */ template ArrayView arrayCast(ArrayView view) { auto out = arrayCast(ArrayView{view}); return ArrayView{const_cast(out.data()), out.size()}; } + /** @brief Array view size */ template constexpr std::size_t arraySize(ArrayView view) { return view.size(); } + /** @overload */ template constexpr std::size_t arraySize(StaticArrayView) { return size_; } + /** @overload */ template constexpr std::size_t arraySize(T(&)[size_]) { return size_; } @@ -359,140 +573,259 @@ namespace Death::Containers template class StaticArrayView { public: + /** @brief Element type */ typedef T Type; enum : std::size_t { Size = size_ }; - /* To avoid ambiguity in certain cases of passing 0 to overloads that take either a ArrayView or std::size_t */ + /** + * @brief Default constructor + * + * Creates a @cpp nullptr @ce view. Copy a non-empty @ref StaticArray + * or @ref StaticArrayView onto the instance to make it useful. + */ template::value>::type> constexpr /*implicit*/ StaticArrayView(U) noexcept : _data{} {} constexpr /*implicit*/ StaticArrayView() noexcept : _data{} {} + /** + * @brief Construct a static view on an array + * @param data Data pointer + * + * The pointer is assumed to contain at least @ref Size elements, but + * it can't be checked in any way. Use @ref StaticArrayView(U(&)[size_]) + * or fixed-size slicing from an @ref ArrayView / @ref Array for more + * safety. + */ template::value && !std::is_same::value>::type> constexpr explicit StaticArrayView(U data) noexcept : _data(data) {} + /** + * @brief Construct a static view on a fixed-size array + * @param data Fixed-size array + * + * Enabled only if @cpp T* @ce is implicitly convertible to @cpp U* @ce. + * Expects that both types have the same size. + */ template::value>::type> constexpr /*implicit*/ StaticArrayView(U(&data)[size_]) noexcept : _data{data} {} + /** + * @brief Construct a static view on an @ref StaticArrayView + * + * Enabled only if @cpp T* @ce is implicitly convertible to @cpp U* @ce. + * Expects that both types have the same size. + */ template::value>::type> constexpr /*implicit*/ StaticArrayView(StaticArrayView view) noexcept : _data{view} { static_assert(sizeof(T) == sizeof(U), "Type sizes are not compatible"); } + /** @brief Construct a view on an external type / from an external representation */ template::type>::from(std::declval()))> constexpr /*implicit*/ StaticArrayView(U&& other) noexcept : StaticArrayView{Implementation::StaticArrayViewConverter::type>::from(std::forward(other))} {} + /** @brief Convert the view to external representation */ template::to(std::declval>()))> constexpr /*implicit*/ operator U() const { return Implementation::StaticArrayViewConverter::to(*this); } #if !defined(DEATH_MSVC2019_COMPATIBILITY) - constexpr explicit operator bool() const { - return _data; - } + /** @brief Whether the view is non-empty */ + constexpr explicit operator bool() const { return _data; } #endif - constexpr /*implicit*/ operator T* () const { - return _data; - } - - constexpr T* data() const { - return _data; - } - - constexpr std::size_t size() const { - return size_; - } - - constexpr bool empty() const { - return !size_; - } - - constexpr T* begin() const { - return _data; - } - constexpr T* cbegin() const { - return _data; - } - - constexpr T* end() const { - return _data + size_; - } - constexpr T* cend() const { - return _data + size_; - } - - T& front() const; - - T& back() const; - + /** @brief Conversion to the underlying type */ + constexpr /*implicit*/ operator T*() const { return _data; } + + /** @brief View data */ + constexpr T* data() const { return _data; } + + /** @brief View size */ + constexpr std::size_t size() const { return size_; } + + /** @brief Whether the view is empty */ + constexpr bool empty() const { return !size_; } + + /** + * @brief Pointer to the first element + * + * @see @ref front(), @ref operator[]() + */ + constexpr T* begin() const { return _data; } + /** @overload */ + constexpr T* cbegin() const { return _data; } + + /** @brief Pointer to (one item after) the last element */ + constexpr T* end() const { return _data + size_; } + /** @overload */ + constexpr T* cend() const { return _data + size_; } + + /** + * @brief First element + * + * Expects there is at least one element. + */ + constexpr T& front() const; + + /** + * @brief Last element + * + * Expects there is at least one element. + */ + constexpr T& back() const; + + /** + * @brief Element access + * @m_since_latest + * + * Expects that @p i is less than @ref size(). + * @see @ref front(), @ref back() + */ + template::value>::type> constexpr T& operator[](U i) const; + + /** @copydoc ArrayView::slice(T*, T*) const */ constexpr ArrayView slice(T* begin, T* end) const { return ArrayView(*this).slice(begin, end); } + /** @overload */ constexpr ArrayView slice(std::size_t begin, std::size_t end) const { return ArrayView(*this).slice(begin, end); } + /** @copydoc ArrayView::sliceSize(T*, std::size_t) const */ + template::value && !std::is_convertible::value>::type> constexpr ArrayView sliceSize(U begin, std::size_t size) const { + return ArrayView(*this).sliceSize(begin, size); + } + /** @overload */ + constexpr ArrayView sliceSize(std::size_t begin, std::size_t size) const { + return ArrayView(*this).sliceSize(begin, size); + } + + /** @copydoc ArrayView::slice(T*) const */ template constexpr StaticArrayView slice(T* begin) const { return ArrayView(*this).template slice(begin); } + /** @overload */ template constexpr StaticArrayView slice(std::size_t begin) const { return ArrayView(*this).template slice(begin); } + /** + * @brief Fixed-size view slice + * + * Expects (at compile time) that @cpp begin_ < end_ @ce and @p end_ is + * not larger than @ref Size. + */ template constexpr StaticArrayView slice() const; + /** + * @brief Fixed-size view slice of given size + * + * Expects (at compile time) that @cpp begin_ + size_ @ce is not larger + * than @ref Size. + */ + template constexpr StaticArrayView sliceSize() const { + return slice(); + } + + /** @copydoc ArrayView::prefix(T*) const */ constexpr ArrayView prefix(T* end) const { return ArrayView(*this).prefix(end); } - constexpr ArrayView prefix(std::size_t end) const { - return ArrayView(*this).prefix(end); + + /** @copydoc ArrayView::suffix(T*) const */ + constexpr ArrayView suffix(T* begin) const { + return ArrayView(*this).suffix(begin); } - template constexpr StaticArrayView prefix() const { - return slice<0, end_>(); + /** @copydoc ArrayView::prefix(std::size_t) const */ + constexpr ArrayView prefix(std::size_t size) const { + return ArrayView(*this).prefix(size); } - constexpr ArrayView suffix(T* begin) const { - return ArrayView(*this).suffix(begin); + /** + * @brief Fixed-size view on the first @p size_ items + * + * Equivalent to @cpp data.slice<0, size_>() @ce. + */ + template constexpr StaticArrayView prefix() const { + return slice<0, size__>(); } - constexpr ArrayView suffix(std::size_t begin) const { - return ArrayView(*this).suffix(begin); + + /** @copydoc ArrayView::exceptPrefix(std::size_t) const */ + constexpr ArrayView exceptPrefix(std::size_t size__) const { + return ArrayView(*this).exceptPrefix(size__); } - template constexpr StaticArrayView suffix() const { - return slice(); + /** + * @brief Fixed-size view except the first @p size__ items + * + * Equivalent to @cpp data.slice() @ce. + */ + template constexpr StaticArrayView exceptPrefix() const { + return slice(); } - constexpr ArrayView except(std::size_t count) const { - return ArrayView(*this).except(count); + /** @copydoc ArrayView::exceptSuffix(std::size_t) const */ + constexpr ArrayView exceptSuffix(std::size_t size) const { + return ArrayView(*this).exceptSuffix(size); } - template constexpr StaticArrayView except() const { - return slice<0, size_ - count>(); + /** + * @brief Fixed-size view except the last @p size__ items + * + * Equivalent to @cpp data.slice<0, Size - size__>() @ce. + */ + template constexpr StaticArrayView exceptSuffix() const { + return slice<0, size_ - size__>(); } private: T* _data; }; + /** + @brief Make a static view on an array + + Convenience alternative to @ref StaticArrayView::StaticArrayView(T*). + */ template constexpr StaticArrayView staticArrayView(T* data) { return StaticArrayView{data}; } + /** + @brief Make a static view on a fixed-size array + + Convenience alternative to @ref StaticArrayView::StaticArrayView(U(&)[size_]). + */ template constexpr StaticArrayView staticArrayView(T(&data)[size]) { return StaticArrayView{data}; } + /** + @brief Make a static view on a view + + Equivalent to the implicit @ref StaticArrayView copy constructor --- it + shouldn't be an error to call @ref staticArrayView() on itself. + */ template constexpr StaticArrayView staticArrayView(StaticArrayView view) { return view; } + /** @brief Make a static view on an external type / from an external representation */ template::type>::from(std::declval()))> constexpr U staticArrayView(T&& other) { return Implementation::ErasedStaticArrayViewConverter::type>::from(std::forward(other)); } + /** + @brief Reinterpret-cast a static array view + + Size of the new array is calculated as @cpp view.size()*sizeof(T)/sizeof(U) @ce. + Expects that both types are [standard layout](http://en.cppreference.com/w/cpp/concept/StandardLayoutType) + and the total byte size doesn't change. + */ template StaticArrayView arrayCast(StaticArrayView view) { static_assert(std::is_standard_layout::value, "The source type is not standard layout"); static_assert(std::is_standard_layout::value, "The target type is not standard layout"); @@ -501,59 +834,62 @@ namespace Death::Containers return StaticArrayView{reinterpret_cast(view.begin())}; } + /** + @brief Reinterpret-cast a statically sized array + + Calls @ref arrayCast(StaticArrayView) with the argument converted to + @ref StaticArrayView of the same type and size. + */ template StaticArrayView arrayCast(T(&data)[size]) { return arrayCast(StaticArrayView{data}); } - template T& ArrayView::front() const { - DEATH_ASSERT(_size != 0, _data[0], "Containers::ArrayView::front(): View is empty"); + template constexpr T& ArrayView::front() const { return _data[0]; } - template T& ArrayView::back() const { - DEATH_ASSERT(_size != 0, _data[_size - 1], "Containers::ArrayView::back(): View is empty"); + template constexpr T& ArrayView::back() const { return _data[_size - 1]; } + template template constexpr T& ArrayView::operator[](const U i) const { + return _data[i]; + } + template constexpr ArrayView ArrayView::slice(T* begin, T* end) const { - return DEATH_CONSTEXPR_ASSERT(_data <= begin && begin <= end && end <= _data + _size, - "Containers::ArrayView::slice(): Slice is out of range"), - ArrayView{begin, std::size_t(end - begin)}; + return ArrayView{begin, std::size_t(end - begin)}; } template constexpr ArrayView ArrayView::slice(std::size_t begin, std::size_t end) const { - return DEATH_CONSTEXPR_ASSERT(begin <= end && end <= _size, - "Containers::ArrayView::slice(): Slice is out of range"), - ArrayView{_data + begin, end - begin}; + return ArrayView{_data + begin, end - begin}; } - template T& StaticArrayView::front() const { + template constexpr T& StaticArrayView::front() const { static_assert(size_ != 0, "View is empty"); return _data[0]; } - template T& StaticArrayView::back() const { + template constexpr T& StaticArrayView::back() const { static_assert(size_ != 0, "View is empty"); return _data[size_ - 1]; } - template template constexpr StaticArrayView ArrayView::slice(T* begin) const { - return DEATH_CONSTEXPR_ASSERT(_data <= begin && begin + viewSize <= _data + _size, - "Containers::ArrayView::slice(): Slice is out of range"), - StaticArrayView{begin}; + template template constexpr T& StaticArrayView::operator[](const U i) const { + return _data[i]; } - template template constexpr StaticArrayView ArrayView::slice(std::size_t begin) const { - return DEATH_CONSTEXPR_ASSERT(begin + viewSize <= _size, - "Containers::ArrayView::slice(): Slice is out of range"), - StaticArrayView{_data + begin}; + template template constexpr StaticArrayView ArrayView::slice(const U begin) const { + return StaticArrayView{begin}; + } + + template template constexpr StaticArrayView ArrayView::slice(std::size_t begin) const { + static_assert(begin + size_ <= _size, "Slice needs to have a positive size"); + return StaticArrayView{_data + begin}; } template template constexpr StaticArrayView ArrayView::slice() const { static_assert(begin_ < end_, "Fixed-size slice needs to have a positive size"); - return DEATH_CONSTEXPR_ASSERT(end_ <= _size, - "Containers::ArrayView::slice(): Slice is out of range"), - StaticArrayView{_data + begin_}; + return StaticArrayView{_data + begin_}; } template template constexpr StaticArrayView StaticArrayView::slice() const { diff --git a/Sources/Shared/Containers/StaticArray.h b/Sources/Shared/Containers/StaticArray.h index dbc43a3f..ab52053a 100644 --- a/Sources/Shared/Containers/StaticArray.h +++ b/Sources/Shared/Containers/StaticArray.h @@ -41,11 +41,22 @@ namespace Death::Containers template class StaticArray { public: + /** @brief Element type */ + typedef T Type; + enum : std::size_t { Size = size_ }; - typedef T Type; + /** + * @brief Construct a default-initialized array + * + * Creates array of given size, the contents are default-initialized + * (i.e., trivial types are not initialized). Because of the differing + * behavior for trivial types it's better to explicitly use either the + * @ref StaticArray(ValueInitT) or the @ref StaticArray(NoInitT) + * variant instead. + */ #if defined(DEATH_TARGET_LIBSTDCXX) && __GNUC__ < 5 && _GLIBCXX_RELEASE < 7 template::value, int>::type = 0> explicit StaticArray(DefaultInitT) {} template::value, int>::type = 0> explicit StaticArray(DefaultInitT) @@ -61,178 +72,336 @@ namespace Death::Containers } #endif + /** + * @brief Construct a value-initialized array + * + * Creates array of given size, the contents are value-initialized + * (i.e., trivial types are zero-initialized, default constructor + * called otherwise). This is the same as @ref StaticArray(). + */ explicit StaticArray(ValueInitT) : _data() {} + /** + * @brief Construct an array without initializing its contents + * + * Creates array of given size, the contents are *not* initialized. + * Useful if you will be overwriting all elements later anyway or if + * you need to call custom constructors in a way that's not expressible + * via any other @ref StaticArray constructor. + * + * For trivial types is equivalent to @ref StaticArray(DefaultInitT). + * For non-trivial types, the class will explicitly call the destructor + * on *all elements* --- which means that for non-trivial types you're + * expected to construct all elements using placement new (or for + * example @ref std::uninitialized_copy()) in order to avoid calling + * destructors on uninitialized memory. + */ explicit StaticArray(NoInitT) {} + /** + * @brief Construct a direct-initialized array + * + * Constructs the array using the @ref StaticArray(NoInitT) constructor + * and then initializes each element with placement new using forwarded + * @p args. + */ template explicit StaticArray(DirectInitT, Args&&... args); + /** + * @brief Construct an in-place-initialized array + * + * The arguments are forwarded to the array constructor. Same as + * @ref StaticArray(Args&&... args). + */ template explicit StaticArray(InPlaceInitT, Args&&... args) : _data{std::forward(args)...} { static_assert(sizeof...(args) == size_, "Containers::StaticArray: Wrong number of initializers"); } + /** + * @brief Construct a value-initialized array + * + * Alias to @ref StaticArray(ValueInitT). + */ explicit StaticArray() : StaticArray{ValueInit} {} + /** + * @brief Construct an in-place-initialized array + * + * Alias to @ref StaticArray(InPlaceInitT, Args&&... args). + */ template::value>::type> /*implicit*/ StaticArray(First&& first, Next&&... next) : StaticArray{InPlaceInit, std::forward(first), std::forward(next)...} {} + /** @brief Copy constructor */ StaticArray(const StaticArray& other) noexcept(std::is_nothrow_copy_constructible::value); + /** @brief Move constructor */ StaticArray(StaticArray&& other) noexcept(std::is_nothrow_move_constructible::value); ~StaticArray(); + /** @brief Copy assignment */ StaticArray& operator=(const StaticArray&) noexcept(std::is_nothrow_copy_constructible::value); + /** @brief Move assignment */ StaticArray& operator=(StaticArray&&) noexcept(std::is_nothrow_move_constructible::value); + /** @brief Convert to external view representation */ template::to(std::declval>()))> /*implicit*/ operator U() { return Implementation::StaticArrayViewConverter::to(*this); } + /** @overload */ template::to(std::declval>()))> constexpr /*implicit*/ operator U() const { return Implementation::StaticArrayViewConverter::to(*this); } - /*implicit*/ operator T* ()& { - return _data; - } - - /*implicit*/ operator const T* () const& { - return _data; - } - - T* data() { - return _data; - } - const T* data() const { + /** @brief Conversion to array type */ + /*implicit*/ operator T*() & { return _data; } - constexpr std::size_t size() const { - return size_; - } - - constexpr bool empty() const { - return !size_; - } - - T* begin() { - return _data; - } - const T* begin() const { - return _data; - } - const T* cbegin() const { + /** @overload */ + /*implicit*/ operator const T*() const & { return _data; } - T* end() { - return _data + size_; - } - const T* end() const { - return _data + size_; - } - const T* cend() const { - return _data + size_; - } - - T& front() { - return _data[0]; - } - const T& front() const { - return _data[0]; - } - - T& back() { - return _data[size_ - 1]; - } - const T& back() const { - return _data[size_ - 1]; - } - + /** @brief Array data */ + T* data() { return _data; } + const T* data() const { return _data; } /**< @overload */ + + /** + * @brief Array size + * + * Equivalent to @ref Size. + */ + constexpr std::size_t size() const { return size_; } + + /** + * @brief Whether the array is empty + * + * Always @cpp true @ce (it's not possible to create a zero-sized C + * array). + */ + constexpr bool empty() const { return !size_; } + + /** @brief Pointer to the first element */ + T* begin() { return _data; } + const T* begin() const { return _data; } /**< @overload */ + const T* cbegin() const { return _data; } /**< @overload */ + + /** @brief Pointer to (one item after) the last element */ + T* end() { return _data + size_; } + const T* end() const { return _data + size_; } /**< @overload */ + const T* cend() const { return _data + size_; } /**< @overload */ + + /** @brief First element */ + T& front() { return _data[0]; } + const T& front() const { return _data[0]; } /**< @overload */ + + /** @brief Last element */ + T& back() { return _data[size_ - 1]; } + const T& back() const { return _data[size_ - 1]; } /**< @overload */ + + /** + * @brief Element access + * + * Expects that @p i is less than @ref size(). + */ + template::value>::type> T& operator[](U i); + template::value>::type> const T& operator[](U i) const; + + /** + * @brief View on a slice + * + * Equivalent to @ref StaticArrayView::slice(T*, T*) const and + * overloads. + */ ArrayView slice(T* begin, T* end) { return ArrayView(*this).slice(begin, end); } + /** @overload */ ArrayView slice(const T* begin, const T* end) const { return ArrayView(*this).slice(begin, end); } + /** @overload */ ArrayView slice(std::size_t begin, std::size_t end) { return ArrayView(*this).slice(begin, end); } + /** @overload */ ArrayView slice(std::size_t begin, std::size_t end) const { return ArrayView(*this).slice(begin, end); } - template StaticArrayView slice(T* begin) { - return ArrayView(*this).template slice(begin); + /** + * @brief View on a slice of given size + * + * Equivalent to @ref StaticArrayView::sliceSize(T*, std::size_t) const + * and overloads. + */ + template::value && !std::is_convertible::value>::type> ArrayView sliceSize(U begin, std::size_t size) { + return ArrayView{*this}.sliceSize(begin, size); } - template StaticArrayView slice(const T* begin) const { - return ArrayView(*this).template slice(begin); + /** @overload */ + template::value && !std::is_convertible::value>::type> ArrayView sliceSize(const U begin, std::size_t size) const { + return ArrayView{*this}.sliceSize(begin, size); } - template StaticArrayView slice(std::size_t begin) { - return ArrayView(*this).template slice(begin); + /** @overload */ + ArrayView sliceSize(std::size_t begin, std::size_t size) { + return ArrayView{*this}.sliceSize(begin, size); } - template StaticArrayView slice(std::size_t begin) const { - return ArrayView(*this).template slice(begin); + /** @overload */ + ArrayView sliceSize(std::size_t begin, std::size_t size) const { + return ArrayView{*this}.sliceSize(begin, size); } + /** + * @brief Fixed-size view on a slice + * + * Equivalent to @ref StaticArrayView::slice(T*) const and overloads. + */ + template::value && !std::is_convertible::value>::type> StaticArrayView slice(U begin) { + return ArrayView(*this).template slice(begin); + } + /** @overload */ + template::value && !std::is_convertible::value>::type> StaticArrayView slice(U begin) const { + return ArrayView(*this).template slice(begin); + } + /** @overload */ + template StaticArrayView slice(std::size_t begin) { + return ArrayView(*this).template slice(begin); + } + /** @overload */ + template StaticArrayView slice(std::size_t begin) const { + return ArrayView(*this).template slice(begin); + } + + /** + * @brief Fixed-size view on a slice + * + * Equivalent to @ref StaticArrayView::slice() const. + */ template StaticArrayView slice() { return StaticArrayView(*this).template slice(); } - + /** @overload */ template StaticArrayView slice() const { return StaticArrayView(*this).template slice(); } - ArrayView prefix(T* end) { - return ArrayView(*this).prefix(end); + /** + * @brief Fixed-size view on a slice of given size + * + * Equivalent to @ref StaticArrayView::sliceSize() const. + */ + template StaticArrayView sliceSize() { + return StaticArrayView(*this).template sliceSize(); } - ArrayView prefix(const T* end) const { - return ArrayView(*this).prefix(end); + /** @overload */ + template StaticArrayView sliceSize() const { + return StaticArrayView(*this).template sliceSize(); } - ArrayView prefix(std::size_t end) { + + /** + * @brief View on a prefix until a pointer + * + * Equivalent to @ref StaticArrayView::prefix(T*) const. + */ + template::value && !std::is_convertible::value>::type> + ArrayView prefix(U end) { return ArrayView(*this).prefix(end); } - ArrayView prefix(std::size_t end) const { + /** @overload */ + template::value && !std::is_convertible::value>::type> + ArrayView prefix(U end) const { return ArrayView(*this).prefix(end); } - template StaticArrayView prefix(); - template StaticArrayView prefix() const; - + /** + * @brief View on a suffix after a pointer + * + * Equivalent to @ref StaticArrayView::suffix(T*) const. + */ ArrayView suffix(T* begin) { return ArrayView(*this).suffix(begin); } + /** @overload */ ArrayView suffix(const T* begin) const { return ArrayView(*this).suffix(begin); } - ArrayView suffix(std::size_t begin) { - return ArrayView(*this).suffix(begin); - } - ArrayView suffix(std::size_t begin) const { - return ArrayView(*this).suffix(begin); - } - - template StaticArrayView suffix() { - return StaticArrayView(*this).template suffix(); - } - template StaticArrayView suffix() const { - return StaticArrayView(*this).template suffix(); - } - - ArrayView except(std::size_t count) { - return ArrayView(*this).except(count); - } - ArrayView except(std::size_t count) const { - return ArrayView(*this).except(count); - } - - template StaticArrayView except() { - return StaticArrayView(*this).template except(); - } - template StaticArrayView except() const { - return StaticArrayView(*this).template except(); + /** + * @brief View on the first @p size items + * + * Equivalent to @ref StaticArrayView::prefix(std::size_t) const. + */ + ArrayView prefix(std::size_t size) { + return ArrayView(*this).prefix(size); + } + /** @overload */ + ArrayView prefix(std::size_t size) const { + return ArrayView(*this).prefix(size); + } + + /** + * @brief Fixed-size view on the first @p size__ items + * + * Equivalent to @ref StaticArrayView::prefix() const and overloads. + */ + template StaticArrayView prefix(); + /** @overload */ + template StaticArrayView prefix() const; + + /** + * @brief View except the first @p size items + * + * Equivalent to @ref StaticArrayView::exceptPrefix(std::size_t) const. + */ + ArrayView exceptPrefix(std::size_t size) { + return ArrayView(*this).exceptPrefix(size); + } + /** @overload */ + ArrayView exceptPrefix(std::size_t size) const { + return ArrayView(*this).exceptPrefix(size); + } + + /** + * @brief Fixed-size view except the first @p size__ items + * + * Equivalent to @ref StaticArrayView::exceptPrefix() const and + * overloads. + */ + template StaticArrayView exceptPrefix() { + return StaticArrayView(*this).template exceptPrefix(); + } + /** @overload */ + template StaticArrayView exceptPrefix() const { + return StaticArrayView(*this).template exceptPrefix(); + } + + /** + * @brief View except the last @p size items + * + * Equivalent to @ref StaticArrayView::exceptSuffix(std::size_t) const. + */ + ArrayView exceptSuffix(std::size_t size) { + return ArrayView(*this).exceptSuffix(size); + } + /** @overload */ + ArrayView exceptSuffix(std::size_t size) const { + return ArrayView(*this).exceptSuffix(size); + } + + /** + * @brief Fixed-size view except the last @p size__ items + * + * Equivalent to @ref StaticArrayView::exceptSuffix() const. + */ + template StaticArrayView exceptSuffix() { + return StaticArrayView(*this).template exceptSuffix(); + } + /** @overload */ + template StaticArrayView exceptSuffix() const { + return StaticArrayView(*this).template exceptSuffix(); } private: @@ -241,30 +410,61 @@ namespace Death::Containers }; }; + /** + @brief Make a view on a @ref StaticArray + + Convenience alternative to converting to an @ref ArrayView explicitly. + */ template constexpr ArrayView arrayView(StaticArray& array) { return ArrayView{array}; } + /** + @brief Make a view on a const @ref StaticArray + + Convenience alternative to converting to an @ref ArrayView explicitly. + */ template constexpr ArrayView arrayView(const StaticArray& array) { return ArrayView{array}; } + /** + @brief Make a static view on a @ref StaticArray + + Convenience alternative to converting to an @ref StaticArrayView explicitly. + */ template constexpr StaticArrayView staticArrayView(StaticArray& array) { return StaticArrayView{array}; } + /** + @brief Make a static view on a const @ref StaticArray + + Convenience alternative to converting to an @ref StaticArrayView explicitly. + */ template constexpr StaticArrayView staticArrayView(const StaticArray& array) { return StaticArrayView{array}; } + /** + @brief Reinterpret-cast a static array + + See @ref arrayCast(StaticArrayView) for more information. + */ template StaticArrayView arrayCast(StaticArray& array) { return arrayCast(staticArrayView(array)); } + /** @overload */ template StaticArrayView arrayCast(const StaticArray& array) { return arrayCast(staticArrayView(array)); } + /** + @brief Static array size + + See @ref arraySize(ArrayView) for more information. + */ template constexpr std::size_t arraySize(const StaticArray&) { return size_; } @@ -276,20 +476,32 @@ namespace Death::Containers } template StaticArray::StaticArray(const StaticArray& other) noexcept(std::is_nothrow_copy_constructible::value) : StaticArray { NoInit } { - for (std::size_t i = 0; i != other.size(); ++i) { - new(&_data[i]) T { other._data[i] }; - } + for (std::size_t i = 0; i != other.size(); ++i) + // Can't use {}, see the GCC 4.8-specific overload for details +#if defined(DEATH_TARGET_GCC) && !defined(DEATH_TARGET_CLANG) && __GNUC__ < 5 + Implementation::construct(_data[i], other._data[i]); +#else + new(_data + i) T{other._data[i]}; +#endif } template StaticArray::StaticArray(StaticArray&& other) noexcept(std::is_nothrow_move_constructible::value) : StaticArray { NoInit } { - for (std::size_t i = 0; i != other.size(); ++i) { - new(&_data[i]) T { std::move(other._data[i]) }; - } + for (std::size_t i = 0; i != other.size(); ++i) + // Can't use {}, see the GCC 4.8-specific overload for details +#if defined(DEATH_TARGET_GCC) && !defined(DEATH_TARGET_CLANG) && __GNUC__ < 5 + Implementation::construct(_data[i], std::move(other._data[i])); +#else + new(&_data[i]) T{std::move(other._data[i])}; +#endif } template StaticArray::~StaticArray() { for (T& i : _data) { i.~T(); +#if defined(DEATH_MSVC2015_COMPATIBILITY) + // Complains i is set but not used for trivially destructible types + static_cast(i); +#endif } } @@ -308,6 +520,14 @@ namespace Death::Containers return *this; } + template template const T& StaticArray::operator[](const U i) const { + return _data[i]; + } + + template template T& StaticArray::operator[](const U i) { + return const_cast(static_cast&>(*this)[i]); + } + template template StaticArrayView StaticArray::prefix() { static_assert(viewSize <= size_, "Prefix size too large"); return StaticArrayView{_data}; @@ -361,6 +581,5 @@ namespace Death::Containers }; template struct ErasedStaticArrayViewConverter> : StaticArrayViewConverter> {}; template struct ErasedStaticArrayViewConverter> : StaticArrayViewConverter> {}; - } } \ No newline at end of file diff --git a/Sources/Shared/Containers/String.cpp b/Sources/Shared/Containers/String.cpp index d2bb044d..b8e3bc26 100644 --- a/Sources/Shared/Containers/String.cpp +++ b/Sources/Shared/Containers/String.cpp @@ -476,11 +476,27 @@ namespace Death::Containers return StringView{*this}.slice(begin, end); } - MutableStringView String::prefix(char* const end) { + MutableStringView String::sliceSizePointerInternal(char* const begin, const std::size_t size) { + return MutableStringView{*this}.sliceSize(begin, size); + } + + StringView String::sliceSizePointerInternal(const char* const begin, const std::size_t size) const { + return StringView{*this}.sliceSize(begin, size); + } + + MutableStringView String::sliceSize(const std::size_t begin, const std::size_t size) { + return MutableStringView{*this}.sliceSize(begin, size); + } + + StringView String::sliceSize(const std::size_t begin, const std::size_t size) const { + return StringView{*this}.sliceSize(begin, size); + } + + MutableStringView String::prefixPointerInternal(char* const end) { return MutableStringView{*this}.prefix(end); } - StringView String::prefix(const char* const end) const { + StringView String::prefixPointerInternal(const char* const end) const { return StringView{*this}.prefix(end); } diff --git a/Sources/Shared/Containers/String.h b/Sources/Shared/Containers/String.h index d3bf6b1e..c4d59d87 100644 --- a/Sources/Shared/Containers/String.h +++ b/Sources/Shared/Containers/String.h @@ -498,6 +498,24 @@ namespace Death::Containers MutableStringView slice(std::size_t begin, std::size_t end); StringView slice(std::size_t begin, std::size_t end) const; + /** + * @brief View on a slice of given size + * + * Equivalent to @ref BasicStringView::sliceSize(). Both arguments are + * expected to be in range. If `begin + size` points to (one item + * after) the end of the original (null-terminated) string, the result + * has @ref StringViewFlag::NullTerminated set. + * @m_keywords{substr()} + */ + template::value && !std::is_convertible::value>::type> MutableStringView sliceSize(T begin, std::size_t size) { + return sliceSizePointerInternal(begin, size); + } + template::value && !std::is_convertible::value>::type> StringView sliceSize(T begin, std::size_t size) const { + return sliceSizePointerInternal(begin, size); + } + MutableStringView sliceSize(std::size_t begin, std::size_t size); + StringView sliceSize(std::size_t begin, std::size_t size) const; + /** * @brief View on a prefix until a pointer * @@ -505,8 +523,12 @@ namespace Death::Containers * points to (one item after) the end of the original (null-terminated) * string, the result has @ref StringViewFlags::NullTerminated set. */ - MutableStringView prefix(char* end); - StringView prefix(const char* end) const; + template::value && !std::is_convertible::value>::type> MutableStringView prefix(T end) { + return prefixPointerInternal(end); + } + template::value && !std::is_convertible::value>::type> StringView prefix(T end) const { + return prefixPointerInternal(end); + } /** * @brief View on a suffix after a pointer @@ -518,33 +540,33 @@ namespace Death::Containers StringView suffix(const char* begin) const; /** - * @brief View on the first @p count bytes + * @brief View on the first @p size bytes * * Equivalent to @ref BasicStringView::prefix(std::size_t) const. If - * @p count is equal to @ref size(), the result has + * @p size is equal to @ref size(), the result has * @ref StringViewFlags::NullTerminated set. */ - MutableStringView prefix(std::size_t count); - StringView prefix(std::size_t count) const; + MutableStringView prefix(std::size_t size); + StringView prefix(std::size_t size) const; /** - * @brief View except the first @p count bytes + * @brief View except the first @p size bytes * * Equivalent to @ref BasicStringView::exceptPrefix(). The result has * always @ref StringViewFlags::NullTerminated set. */ - MutableStringView exceptPrefix(std::size_t count); - StringView exceptPrefix(std::size_t count) const; + MutableStringView exceptPrefix(std::size_t size); + StringView exceptPrefix(std::size_t size) const; /** - * @brief View except the last @p count bytes + * @brief View except the last @p size bytes * * Equivalent to @ref BasicStringView::exceptSuffix(). If - * @p count is @cpp 0 @ce, the result has + * @p size is @cpp 0 @ce, the result has * @ref StringViewFlags::NullTerminated set. */ - MutableStringView exceptSuffix(std::size_t count); - StringView exceptSuffix(std::size_t count) const; + MutableStringView exceptSuffix(std::size_t size); + StringView exceptSuffix(std::size_t size) const; /** * @brief Split on given character @@ -611,7 +633,6 @@ namespace Death::Containers * Equivalent to @ref BasicStringView::join(). */ String join(ArrayView strings) const; - /** @overload */ String join(std::initializer_list strings) const; @@ -621,8 +642,6 @@ namespace Death::Containers * Equivalent to @ref BasicStringView::joinWithoutEmptyParts(). */ String joinWithoutEmptyParts(ArrayView strings) const; - - /** @overload */ String joinWithoutEmptyParts(std::initializer_list strings) const; /** @@ -871,6 +890,11 @@ namespace Death::Containers void destruct(); Pair dataInternal() const; + MutableStringView sliceSizePointerInternal(char* begin, std::size_t size); + StringView sliceSizePointerInternal(const char* begin, std::size_t size) const; + MutableStringView prefixPointerInternal(char* end); + StringView prefixPointerInternal(const char* end) const; + /* Small string optimization. Following size restrictions from StringView (which uses the top two bits for marking global and null-terminated views), we can use the second highest bit of the diff --git a/Sources/Shared/Containers/StringView.h b/Sources/Shared/Containers/StringView.h index 76b92ebd..052d87b6 100644 --- a/Sources/Shared/Containers/StringView.h +++ b/Sources/Shared/Containers/StringView.h @@ -207,9 +207,7 @@ namespace Death::Containers * @ref flags() and @ref StringViewFlags::NullTerminated to check for * the presence of a null terminator. */ - constexpr T* data() const { - return _data; - } + constexpr T* data() const { return _data; } /** * @brief String size @@ -230,22 +228,14 @@ namespace Death::Containers /** * @brief Pointer to the first byte */ - constexpr T* begin() const { - return _data; - } - constexpr T* cbegin() const { - return _data; - } + constexpr T* begin() const { return _data; } + constexpr T* cbegin() const { return _data; } /** * @brief Pointer to (one item after) the last byte */ - constexpr T* end() const { - return _data + (_sizePlusFlags & ~Implementation::StringViewSizeMask); - } - constexpr T* cend() const { - return _data + (_sizePlusFlags & ~Implementation::StringViewSizeMask); - } + constexpr T* end() const { return _data + (_sizePlusFlags & ~Implementation::StringViewSizeMask); } + constexpr T* cend() const { return _data + (_sizePlusFlags & ~Implementation::StringViewSizeMask); } /** * @brief First byte @@ -262,9 +252,7 @@ namespace Death::Containers constexpr T& back() const; /** @brief Element access */ - constexpr T& operator[](std::size_t i) const { - return _data[i]; - } + constexpr T& operator[](std::size_t i) const; /** * @brief View slice @@ -279,14 +267,28 @@ namespace Death::Containers /** @overload */ constexpr BasicStringView slice(std::size_t begin, std::size_t end) const; + /** + * @brief View slice of given size + * + * Equivalent to @cpp data.slice(begin, begin + size) @ce. + */ + template::value && !std::is_convertible::value>::type> constexpr BasicStringView sliceSize(U begin, std::size_t size) const { + return slice(begin, begin + size); + } + + /** @overload */ + constexpr BasicStringView sliceSize(std::size_t begin, std::size_t size) const { + return slice(begin, begin + size); + } + /** * @brief View prefix until a pointer * * Equivalent to @cpp string.slice(string.begin(), end) @ce. If @p end * is @cpp nullptr @ce, returns zero-sized @cpp nullptr @ce view. */ - constexpr BasicStringView prefix(T* end) const { - return end ? slice(_data, end) : BasicStringView{}; + template::value && !std::is_convertible::value>::type> constexpr BasicStringView prefix(U end) const { + return static_cast(end) ? slice(_data, end) : BasicStringView{}; } /** @@ -832,6 +834,10 @@ namespace Death::Containers } } + template constexpr T& BasicStringView::operator[](const std::size_t i) const { + return _data[i]; + } + template constexpr T& BasicStringView::front() const { return _data[0]; } diff --git a/Sources/Shared/IO/FileSystem.cpp b/Sources/Shared/IO/FileSystem.cpp index 3edd7c05..eea61c1d 100644 --- a/Sources/Shared/IO/FileSystem.cpp +++ b/Sources/Shared/IO/FileSystem.cpp @@ -848,7 +848,7 @@ namespace Death::IO { // Take ownership first if not already (e.g., directly from `String::nullTerminatedView()`) if (!path.isSmall() && path.deleter()) { - path = String { path }; + path = String{path}; } for (char& c : path) { if (c == '/') { diff --git a/Sources/nCine/Primitives/AABB.h b/Sources/nCine/Primitives/AABB.h index 7c435f8a..1b159bfa 100644 --- a/Sources/nCine/Primitives/AABB.h +++ b/Sources/nCine/Primitives/AABB.h @@ -7,7 +7,7 @@ namespace nCine { /// A template-based Axis-Aligned Bounding Box in a two dimensional space - template + template class AABB { public: @@ -73,27 +73,27 @@ namespace nCine using AABBf = AABB; using AABBi = AABB; - template + template inline Vector2 AABB::GetCenter() const { return Vector2((L + R) / 2, (T + B) / 2); } - template + template inline Vector2 AABB::GetExtents() const { return Vector2((R - L) / 2, (B - T) / 2); } - template + template inline S AABB::GetPerimeter() const { - float wx = R - L; - float wy = B - T; + S wx = R - L; + S wy = B - T; return 2 * (wx + wy); } - template + template inline bool AABB::Contains(S px, S py) const { // Using epsilon to try and guard against float rounding errors @@ -101,25 +101,31 @@ namespace nCine (py > (T + std::numeric_limits::epsilon) && py < (B - std::numeric_limits::epsilon))); } - template + template<> + inline bool AABB::Contains(std::int32_t px, std::int32_t py) const + { + return (px >= L && px <= R && py >= T && py <= B); + } + + template inline bool AABB::Contains(const Vector2& p) const { - return contains(p.X, p.Y); + return Contains(p.X, p.Y); } - template + template inline bool AABB::Contains(const AABB& aabb) const { return (L <= aabb.L && T <= aabb.T && aabb.R <= R && aabb.B <= B); } - template + template inline bool AABB::Overlaps(const AABB& aabb) const { return (L <= aabb.R && T <= aabb.B && R >= aabb.L && B >= aabb.T); } - template + template inline AABB AABB::Intersect(const AABB& a, const AABB& b) { if (!a.Overlaps(b)) { @@ -129,58 +135,55 @@ namespace nCine return AABB(std::max(a.L, b.L), std::max(a.T, b.T), std::min(a.R, b.R), std::min(a.B, b.B)); } - template + template inline AABB AABB::Combine(const AABB& a, const AABB& b) { return AABB(std::min(a.L, b.L), std::min(a.T, b.T), std::max(a.R, b.R), std::max(a.B, b.B)); } - template + template inline bool AABB::operator==(const AABB& aabb) const { return (L == aabb.L && T == aabb.T && R == aabb.R && B == aabb.B); } - template + template inline bool AABB::operator!=(const AABB& aabb) const { return (L != aabb.L || T != aabb.T || R != aabb.R || B != aabb.B); } - template + template inline AABB& AABB::operator+=(const Vector2& v) { L += v.X; T += v.Y; R += v.X; B += v.Y; - return *this; } - template + template inline AABB& AABB::operator-=(const Vector2& v) { L -= v.X; T -= v.Y; R -= v.X; B -= v.Y; - return *this; } - template + template inline AABB AABB::operator+(const Vector2& v) const { return AABB(L + v.X, T + v.Y, R + v.X, B + v.Y); } - template + template inline AABB AABB::operator-(const Vector2& v) const { return AABB(L - v.X, T - v.Y, R - v.X, B - v.Y); } - } diff --git a/android/app/src/main/cpp/CMakeLists.txt b/android/app/src/main/cpp/CMakeLists.txt index 1b9c961b..c814bf4b 100644 --- a/android/app/src/main/cpp/CMakeLists.txt +++ b/android/app/src/main/cpp/CMakeLists.txt @@ -4,6 +4,7 @@ project(nCine-Android-${ANDROID_ABI}) # Check if we can use IFUNC for CPU dispatch. This part is already in `ncine_options.cmake`, but we need to check it also against Android NDK toolchain. if(DEATH_CPU_USE_RUNTIME_DISPATCH) include(CheckCXXSourceCompiles) + set(CMAKE_REQUIRED_QUIET ON) check_cxx_source_compiles("\ int fooImplementation() { return 42; } #if defined(__ANDROID_API__) && __ANDROID_API__ < 30 @@ -15,6 +16,7 @@ extern \"C\" int(*fooDispatcher())() { int foo() __attribute__((ifunc(\"fooDispatcher\"))); int main() { return foo() - 42; }\ " _DEATH_CPU_CAN_USE_IFUNC) + set(CMAKE_REQUIRED_QUIET OFF) if(_DEATH_CPU_CAN_USE_IFUNC) # DEATH_CPU_USE_RUNTIME_DISPATCH is already enabled set(DEATH_CPU_USE_IFUNC ON) diff --git a/cmake/ncine_extra_sources.cmake b/cmake/ncine_extra_sources.cmake index f4b833d4..3aaa188d 100644 --- a/cmake/ncine_extra_sources.cmake +++ b/cmake/ncine_extra_sources.cmake @@ -701,16 +701,19 @@ if(WITH_MULTIPLAYER) list(APPEND HEADERS ${NCINE_SOURCE_DIR}/Jazz2/Actors/RemoteActor.h + ${NCINE_SOURCE_DIR}/Jazz2/Multiplayer/ConnectionResult.h ${NCINE_SOURCE_DIR}/Jazz2/Multiplayer/INetworkHandler.h ${NCINE_SOURCE_DIR}/Jazz2/Multiplayer/MultiLevelHandler.h ${NCINE_SOURCE_DIR}/Jazz2/Multiplayer/NetworkManager.h ${NCINE_SOURCE_DIR}/Jazz2/Multiplayer/PacketTypes.h ${NCINE_SOURCE_DIR}/Jazz2/Multiplayer/Peer.h + ${NCINE_SOURCE_DIR}/Jazz2/Multiplayer/Reason.h ${NCINE_SOURCE_DIR}/Jazz2/Multiplayer/Backends/enet.h ) list(APPEND SOURCES ${NCINE_SOURCE_DIR}/Jazz2/Actors/RemoteActor.cpp + ${NCINE_SOURCE_DIR}/Jazz2/Multiplayer/ConnectionResult.cpp ${NCINE_SOURCE_DIR}/Jazz2/Multiplayer/MultiLevelHandler.cpp ${NCINE_SOURCE_DIR}/Jazz2/Multiplayer/NetworkManager.cpp )