From fc4036af809fcdfec64d8b8d24b8569ba21f4326 Mon Sep 17 00:00:00 2001 From: TellowKrinkle Date: Tue, 27 Jun 2023 23:50:16 -0500 Subject: [PATCH 001/120] VideoBackends:Metal: Multi render target support --- Source/Core/VideoBackends/Metal/MTLGfx.mm | 14 +++--- .../VideoBackends/Metal/MTLObjectCache.mm | 7 +++ .../VideoBackends/Metal/MTLStateTracker.h | 1 - .../VideoBackends/Metal/MTLStateTracker.mm | 35 ++------------ Source/Core/VideoBackends/Metal/MTLTexture.h | 26 +++++++--- Source/Core/VideoBackends/Metal/MTLTexture.mm | 47 ++++++++++++++++++- 6 files changed, 82 insertions(+), 48 deletions(-) diff --git a/Source/Core/VideoBackends/Metal/MTLGfx.mm b/Source/Core/VideoBackends/Metal/MTLGfx.mm index 1137c45e5f..cf3c134cdb 100644 --- a/Source/Core/VideoBackends/Metal/MTLGfx.mm +++ b/Source/Core/VideoBackends/Metal/MTLGfx.mm @@ -94,12 +94,12 @@ Metal::Gfx::CreateStagingTexture(StagingTextureType type, const TextureConfig& c std::unique_ptr Metal::Gfx::CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment, - std::vector) + std::vector additional_color_attachments) { AbstractTexture* const either_attachment = color_attachment ? color_attachment : depth_attachment; return std::make_unique( - color_attachment, depth_attachment, either_attachment->GetWidth(), - either_attachment->GetHeight(), either_attachment->GetLayers(), + color_attachment, depth_attachment, std::move(additional_color_attachments), + either_attachment->GetWidth(), either_attachment->GetHeight(), either_attachment->GetLayers(), either_attachment->GetSamples()); } @@ -437,7 +437,7 @@ void Metal::Gfx::BindBackbuffer(const ClearColor& clear_color) CheckForSurfaceChange(); CheckForSurfaceResize(); m_drawable = MRCRetain([m_layer nextDrawable]); - m_bb_texture->SetMTLTexture(MRCRetain([m_drawable texture])); + m_backbuffer->UpdateBackbufferTexture([m_drawable texture]); SetAndClearFramebuffer(m_backbuffer.get(), clear_color); } } @@ -460,7 +460,7 @@ void Metal::Gfx::PresentBackbuffer() else [g_state_tracker->GetRenderCmdBuf() addScheduledHandler:[drawable = std::move(m_drawable)](id) { [drawable present]; }]; - m_bb_texture->SetMTLTexture(nullptr); + m_backbuffer->UpdateBackbufferTexture(nullptr); m_drawable = nullptr; } g_state_tracker->FlushEncoders(); @@ -491,8 +491,8 @@ void Metal::Gfx::SetupSurface() TextureConfig cfg(info.width, info.height, 1, 1, 1, info.format, AbstractTextureFlag_RenderTarget); m_bb_texture = std::make_unique(nullptr, cfg); - m_backbuffer = std::make_unique(m_bb_texture.get(), nullptr, // - info.width, info.height, 1, 1); + m_backbuffer = std::make_unique( + m_bb_texture.get(), nullptr, std::vector{}, info.width, info.height, 1, 1); if (g_presenter) g_presenter->SetBackbuffer(info); diff --git a/Source/Core/VideoBackends/Metal/MTLObjectCache.mm b/Source/Core/VideoBackends/Metal/MTLObjectCache.mm index 2a9387edeb..5d29f06e88 100644 --- a/Source/Core/VideoBackends/Metal/MTLObjectCache.mm +++ b/Source/Core/VideoBackends/Metal/MTLObjectCache.mm @@ -313,6 +313,8 @@ public: framebuffer.color_texture_format = cfg.framebuffer_state.color_texture_format.Value(); framebuffer.depth_texture_format = cfg.framebuffer_state.depth_texture_format.Value(); framebuffer.samples = cfg.framebuffer_state.samples.Value(); + framebuffer.additional_color_attachment_count = + cfg.framebuffer_state.additional_color_attachment_count.Value(); blend.colorupdate = cfg.blending_state.colorupdate.Value(); blend.alphaupdate = cfg.blending_state.alphaupdate.Value(); if (cfg.blending_state.blendenable) @@ -426,6 +428,11 @@ public: } [desc setRasterSampleCount:fs.samples]; [color0 setPixelFormat:Util::FromAbstract(fs.color_texture_format)]; + if (u32 cnt = fs.additional_color_attachment_count) + { + for (u32 i = 0; i < cnt; i++) + [[desc colorAttachments] setObject:color0 atIndexedSubscript:i + 1]; + } [desc setDepthAttachmentPixelFormat:Util::FromAbstract(fs.depth_texture_format)]; if (Util::HasStencil(fs.depth_texture_format)) [desc setStencilAttachmentPixelFormat:Util::FromAbstract(fs.depth_texture_format)]; diff --git a/Source/Core/VideoBackends/Metal/MTLStateTracker.h b/Source/Core/VideoBackends/Metal/MTLStateTracker.h index 54e16cac7e..f3fa306dd7 100644 --- a/Source/Core/VideoBackends/Metal/MTLStateTracker.h +++ b/Source/Core/VideoBackends/Metal/MTLStateTracker.h @@ -182,7 +182,6 @@ private: MRCOwned> m_last_render_cmdbuf; MRCOwned> m_current_render_encoder; MRCOwned> m_current_compute_encoder; - MRCOwned m_render_pass_desc[3]; MRCOwned m_resolve_pass_desc; Framebuffer* m_current_framebuffer; CPUBuffer m_texture_upload_buffer; diff --git a/Source/Core/VideoBackends/Metal/MTLStateTracker.mm b/Source/Core/VideoBackends/Metal/MTLStateTracker.mm index f699657ade..8546744a4d 100644 --- a/Source/Core/VideoBackends/Metal/MTLStateTracker.mm +++ b/Source/Core/VideoBackends/Metal/MTLStateTracker.mm @@ -107,12 +107,6 @@ Metal::StateTracker::StateTracker() : m_backref(std::make_shared(this)) { m_flags.should_apply_label = true; m_fence = MRCTransfer([g_device newFence]); - for (MRCOwned& rpdesc : m_render_pass_desc) - { - rpdesc = MRCTransfer([MTLRenderPassDescriptor new]); - [[rpdesc depthAttachment] setStoreAction:MTLStoreActionStore]; - [[rpdesc stencilAttachment] setStoreAction:MTLStoreActionStore]; - } m_resolve_pass_desc = MRCTransfer([MTLRenderPassDescriptor new]); auto color0 = [[m_resolve_pass_desc colorAttachments] objectAtIndexedSubscript:0]; [color0 setLoadAction:MTLLoadActionLoad]; @@ -299,27 +293,8 @@ void Metal::StateTracker::SetCurrentFramebuffer(Framebuffer* framebuffer) MTLRenderPassDescriptor* Metal::StateTracker::GetRenderPassDescriptor(Framebuffer* framebuffer, MTLLoadAction load_action) { - const AbstractTextureFormat depth_fmt = framebuffer->GetDepthFormat(); - MTLRenderPassDescriptor* desc; - if (depth_fmt == AbstractTextureFormat::Undefined) - desc = m_render_pass_desc[0]; - else if (!Util::HasStencil(depth_fmt)) - desc = m_render_pass_desc[1]; - else - desc = m_render_pass_desc[2]; - desc.colorAttachments[0].texture = framebuffer->GetColor(); - desc.colorAttachments[0].loadAction = load_action; - if (depth_fmt != AbstractTextureFormat::Undefined) - { - desc.depthAttachment.texture = framebuffer->GetDepth(); - desc.depthAttachment.loadAction = load_action; - if (Util::HasStencil(depth_fmt)) - { - desc.stencilAttachment.texture = framebuffer->GetDepth(); - desc.stencilAttachment.loadAction = load_action; - } - } - return desc; + framebuffer->SetLoadAction(load_action); + return framebuffer->PassDesc(); } void Metal::StateTracker::BeginClearRenderPass(MTLClearColor color, float depth) @@ -328,11 +303,9 @@ void Metal::StateTracker::BeginClearRenderPass(MTLClearColor color, float depth) MTLRenderPassDescriptor* desc = GetRenderPassDescriptor(framebuffer, MTLLoadActionClear); desc.colorAttachments[0].clearColor = color; if (framebuffer->GetDepthFormat() != AbstractTextureFormat::Undefined) - { desc.depthAttachment.clearDepth = depth; - if (Util::HasStencil(framebuffer->GetDepthFormat())) - desc.stencilAttachment.clearStencil = 0; - } + for (size_t i = 0; i < framebuffer->NumAdditionalColorTextures(); i++) + desc.colorAttachments[i + 1].clearColor = color; BeginRenderPass(desc); } diff --git a/Source/Core/VideoBackends/Metal/MTLTexture.h b/Source/Core/VideoBackends/Metal/MTLTexture.h index 0624661414..908f281e04 100644 --- a/Source/Core/VideoBackends/Metal/MTLTexture.h +++ b/Source/Core/VideoBackends/Metal/MTLTexture.h @@ -29,7 +29,6 @@ public: u32 layer) override; id GetMTLTexture() const { return m_tex; } - void SetMTLTexture(MRCOwned> tex) { m_tex = std::move(tex); } private: MRCOwned> m_tex; @@ -61,17 +60,30 @@ private: class Framebuffer final : public AbstractFramebuffer { public: - Framebuffer(AbstractTexture* color, AbstractTexture* depth, u32 width, u32 height, u32 layers, - u32 samples); + Framebuffer(AbstractTexture* color, AbstractTexture* depth, + std::vector additonal_color_textures, // + u32 width, u32 height, u32 layers, u32 samples); ~Framebuffer(); - id GetColor() const + MTLRenderPassDescriptor* PassDesc() const { return m_pass_descriptor; } + + size_t NumAdditionalColorTextures() const { return m_additional_color_textures.size(); } + + void SetLoadAction(MTLLoadAction action) { - return static_cast(GetColorAttachment())->GetMTLTexture(); + if (m_current_load_action != action) + ActualSetLoadAction(action); } - id GetDepth() const + + void UpdateBackbufferTexture(id tex) { - return static_cast(GetDepthAttachment())->GetMTLTexture(); + [m_pass_descriptor colorAttachments][0].texture = tex; } + +private: + MRCOwned m_pass_descriptor; + std::vector m_additional_color_textures; + MTLLoadAction m_current_load_action = MTLLoadActionLoad; + void ActualSetLoadAction(MTLLoadAction action); }; } // namespace Metal diff --git a/Source/Core/VideoBackends/Metal/MTLTexture.mm b/Source/Core/VideoBackends/Metal/MTLTexture.mm index c9750eb1a5..ea4c7fb5ab 100644 --- a/Source/Core/VideoBackends/Metal/MTLTexture.mm +++ b/Source/Core/VideoBackends/Metal/MTLTexture.mm @@ -189,13 +189,56 @@ void Metal::StagingTexture::Flush() m_wait_buffer = nullptr; } -Metal::Framebuffer::Framebuffer(AbstractTexture* color, AbstractTexture* depth, // +static void InitDesc(id desc, AbstractTexture* tex) +{ + [desc setTexture:static_cast(tex)->GetMTLTexture()]; + [desc setLoadAction:MTLLoadActionLoad]; + [desc setStoreAction:MTLStoreActionStore]; +} + +static void InitStencilDesc(MTLRenderPassStencilAttachmentDescriptor* desc, AbstractTexture* tex) +{ + InitDesc(desc, tex); + [desc setClearStencil:0]; +} + +Metal::Framebuffer::Framebuffer(AbstractTexture* color, AbstractTexture* depth, + std::vector additonal_color_textures, // u32 width, u32 height, u32 layers, u32 samples) : AbstractFramebuffer(color, depth, {}, color ? color->GetFormat() : AbstractTextureFormat::Undefined, // depth ? depth->GetFormat() : AbstractTextureFormat::Undefined, // - width, height, layers, samples) + width, height, layers, samples), + m_additional_color_textures(std::move(additonal_color_textures)) { + m_pass_descriptor = MRCTransfer([MTLRenderPassDescriptor new]); + MTLRenderPassDescriptor* desc = m_pass_descriptor; + if (color) + InitDesc(desc.colorAttachments[0], color); + if (depth) + { + InitDesc(desc.depthAttachment, depth); + if (Util::HasStencil(depth->GetFormat())) + InitStencilDesc(desc.stencilAttachment, depth); + } + for (size_t i = 0; i < m_additional_color_textures.size(); i++) + InitDesc(desc.colorAttachments[i + 1], m_additional_color_textures[i]); } Metal::Framebuffer::~Framebuffer() = default; + +void Metal::Framebuffer::ActualSetLoadAction(MTLLoadAction action) +{ + m_current_load_action = action; + AbstractTextureFormat depth_fmt = GetDepthFormat(); + MTLRenderPassDescriptor* desc = m_pass_descriptor; + desc.colorAttachments[0].loadAction = action; + if (depth_fmt != AbstractTextureFormat::Undefined) + { + desc.depthAttachment.loadAction = action; + if (Util::HasStencil(depth_fmt)) + desc.stencilAttachment.loadAction = action; + } + for (size_t i = 0; i < NumAdditionalColorTextures(); i++) + desc.colorAttachments[i + 1].loadAction = action; +} From 7a7e0ab2f59cf71266e2219d44c87d8f0b597a4b Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 17 Jun 2023 15:27:23 -0400 Subject: [PATCH 002/120] Fixed Leaderboard Scored message format Refactored the message displayed when a leaderboard is submitted to so that it properly generates and uses a FormattedValue. --- Source/Core/Core/AchievementManager.cpp | 21 +++++++++++++++++---- Source/Core/Core/AchievementManager.h | 10 ++++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index ef23dcf163..74bd257311 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -585,7 +585,7 @@ void AchievementManager::ActivateDeactivateAchievement(AchievementId id, bool en rc_runtime_deactivate_achievement(&m_runtime, id); } -RichPresence AchievementManager::GenerateRichPresence() +AchievementManager::RichPresence AchievementManager::GenerateRichPresence() { RichPresence rp_buffer; Core::RunAsCPUThread([&] { @@ -716,9 +716,22 @@ void AchievementManager::HandleLeaderboardTriggeredEvent(const rc_runtime_event_ { if (m_game_data.leaderboards[ix].id == runtime_event->id) { - OSD::AddMessage(fmt::format("Scored {} on leaderboard: {}", runtime_event->value, - m_game_data.leaderboards[ix].title), - OSD::Duration::VERY_LONG, OSD::Color::YELLOW); + FormattedValue value{}; + rc_runtime_format_lboard_value(value.data(), static_cast(value.size()), + runtime_event->value, m_game_data.leaderboards[ix].format); + if (std::find(value.begin(), value.end(), '\0') == value.end()) + { + OSD::AddMessage(fmt::format("Scored {} on leaderboard: {}", + std::string_view{value.data(), value.size()}, + m_game_data.leaderboards[ix].title), + OSD::Duration::VERY_LONG, OSD::Color::YELLOW); + } + else + { + OSD::AddMessage(fmt::format("Scored {} on leaderboard: {}", value.data(), + m_game_data.leaderboards[ix].title), + OSD::Duration::VERY_LONG, OSD::Color::YELLOW); + } break; } } diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index 14b6b0ec77..a2e159c7e3 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -19,10 +19,6 @@ #include "Common/Event.h" #include "Common/WorkQueueThread.h" -using AchievementId = u32; -constexpr size_t RP_SIZE = 256; -using RichPresence = std::array; - namespace Core { class System; @@ -53,6 +49,12 @@ public: u32 soft_points; }; + using AchievementId = u32; + static constexpr size_t FORMAT_SIZE = 24; + using FormattedValue = std::array; + static constexpr size_t RP_SIZE = 256; + using RichPresence = std::array; + struct UnlockStatus { AchievementId game_data_index = 0; From f78ba9ac557798ce451283f50a0dc19c10363fd0 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 22 Oct 2022 14:04:00 +0200 Subject: [PATCH 003/120] JitArm64: Never check downcount on block entry Jumping between linked blocks currently works as follows: First, at the end of the first block, we check if the downcount is greater than zero. If it is, we jump to the `normalEntry` of the block. So far so good. But if the downcount wasn't greater than zero, we jump to the `checkedEntry` of the block, which checks the downcount *again* and then jumps to `do_timing` if it's less than zero (which seems like an off by one error - Jit64 doesn't do anything like this). This second check is rather redundant. Let's jump to `do_timing` where we previously jumped to `checkedEntry`. Jit64 doesn't check the downcount on block entry. See 5236dc3. --- Source/Core/Core/PowerPC/JitArm64/Jit.cpp | 31 ++++---- .../Core/PowerPC/JitArm64/JitArm64Cache.cpp | 79 ++++++++++++------- .../Core/PowerPC/JitArm64/JitArm64Cache.h | 3 + 3 files changed, 69 insertions(+), 44 deletions(-) diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp index ef7737d69c..f8b2a99f72 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp @@ -361,11 +361,14 @@ void JitArm64::WriteExit(u32 destination, bool LK, u32 exit_address_after_return LK &= m_enable_blr_optimization; + const u8* host_address_after_return; if (LK) { - // Push {ARM_PC+20; PPC_PC} on the stack + // Push {ARM_PC; PPC_PC} on the stack MOVI2R(ARM64Reg::X1, exit_address_after_return); - ADR(ARM64Reg::X0, 20); + constexpr s32 adr_offset = JitArm64BlockCache::BLOCK_LINK_SIZE + sizeof(u32) * 2; + host_address_after_return = GetCodePtr() + adr_offset; + ADR(ARM64Reg::X0, adr_offset); STP(IndexType::Pre, ARM64Reg::X0, ARM64Reg::X1, ARM64Reg::SP, -16); } @@ -381,6 +384,8 @@ void JitArm64::WriteExit(u32 destination, bool LK, u32 exit_address_after_return if (LK) { + DEBUG_ASSERT(GetCodePtr() == host_address_after_return || HasWriteFailed()); + // Write the regular exit node after the return. linkData.exitAddress = exit_address_after_return; linkData.exitPtrs = GetWritableCodePtr(); @@ -411,10 +416,13 @@ void JitArm64::WriteExit(Arm64Gen::ARM64Reg dest, bool LK, u32 exit_address_afte { // Push {ARM_PC, PPC_PC} on the stack MOVI2R(ARM64Reg::X1, exit_address_after_return); - ADR(ARM64Reg::X0, 12); + constexpr s32 adr_offset = sizeof(u32) * 3; + const u8* host_address_after_return = GetCodePtr() + adr_offset; + ADR(ARM64Reg::X0, adr_offset); STP(IndexType::Pre, ARM64Reg::X0, ARM64Reg::X1, ARM64Reg::SP, -16); BL(dispatcher); + DEBUG_ASSERT(GetCodePtr() == host_address_after_return || HasWriteFailed()); // Write the regular exit node after the return. JitBlock* b = js.curBlock; @@ -440,11 +448,14 @@ void JitArm64::FakeLKExit(u32 exit_address_after_return) ARM64Reg after_reg = gpr.GetReg(); ARM64Reg code_reg = gpr.GetReg(); MOVI2R(after_reg, exit_address_after_return); - ADR(EncodeRegTo64(code_reg), 12); + constexpr s32 adr_offset = sizeof(u32) * 3; + const u8* host_address_after_return = GetCodePtr() + adr_offset; + ADR(EncodeRegTo64(code_reg), adr_offset); STP(IndexType::Pre, EncodeRegTo64(code_reg), EncodeRegTo64(after_reg), ARM64Reg::SP, -16); gpr.Unlock(after_reg, code_reg); FixupBranch skip_exit = BL(); + DEBUG_ASSERT(GetCodePtr() == host_address_after_return || HasWriteFailed()); gpr.Unlock(ARM64Reg::W30); // Write the regular exit node after the return. @@ -839,17 +850,7 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC) u8* const start = GetWritableCodePtr(); b->checkedEntry = start; - - // Downcount flag check, Only valid for linked blocks - { - FixupBranch bail = B(CC_PL); - MOVI2R(DISPATCHER_PC, js.blockStart); - B(do_timing); - SetJumpTarget(bail); - } - - // Normal entry doesn't need to check for downcount. - b->normalEntry = GetWritableCodePtr(); + b->normalEntry = start; // Conditionally add profiling code. if (jo.profile_blocks) diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp b/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp index 6551ed2d45..f0e6922df8 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp @@ -22,52 +22,73 @@ void JitArm64BlockCache::Init() void JitArm64BlockCache::WriteLinkBlock(Arm64Gen::ARM64XEmitter& emit, const JitBlock::LinkData& source, const JitBlock* dest) { + const u8* start = emit.GetCodePtr(); + if (!dest) { - // Use a fixed amount of instructions, so we can assume to use 3 instructions on patching. - emit.MOVZ(DISPATCHER_PC, source.exitAddress & 0xFFFF, ShiftAmount::Shift0); - emit.MOVK(DISPATCHER_PC, source.exitAddress >> 16, ShiftAmount::Shift16); - + emit.MOVI2R(DISPATCHER_PC, source.exitAddress); if (source.call) + { + while (emit.GetCodePtr() < start + BLOCK_LINK_FAST_BL_OFFSET && !emit.HasWriteFailed()) + emit.NOP(); emit.BL(m_jit.GetAsmRoutines()->dispatcher); + } else + { emit.B(m_jit.GetAsmRoutines()->dispatcher); - return; + } + } + else + { + if (source.call) + { + // The "fast" BL should be the last instruction, so that the return address matches the + // address that was pushed onto the stack by the function that called WriteLinkBlock + FixupBranch fast = emit.B(CC_GT); + emit.MOVI2R(DISPATCHER_PC, source.exitAddress); + emit.BL(m_jit.GetAsmRoutines()->do_timing); + while (emit.GetCodePtr() < start + BLOCK_LINK_FAST_BL_OFFSET && !emit.HasWriteFailed()) + emit.BRK(101); + emit.SetJumpTarget(fast); + emit.BL(dest->normalEntry); + } + else + { + // Are we able to jump directly to the block? + s64 block_distance = ((s64)dest->normalEntry - (s64)emit.GetCodePtr()) >> 2; + if (block_distance >= -0x40000 && block_distance <= 0x3FFFF) + { + emit.B(CC_GT, dest->normalEntry); + emit.MOVI2R(DISPATCHER_PC, source.exitAddress); + emit.B(m_jit.GetAsmRoutines()->do_timing); + } + else + { + FixupBranch slow = emit.B(CC_LE); + emit.B(dest->normalEntry); + emit.SetJumpTarget(slow); + emit.MOVI2R(DISPATCHER_PC, source.exitAddress); + emit.B(m_jit.GetAsmRoutines()->do_timing); + } + } } - if (source.call) + // Use a fixed number of instructions so we have enough room for any patching needed later. + const u8* end = start + BLOCK_LINK_SIZE; + while (emit.GetCodePtr() < end) { - // The "fast" BL must be the third instruction. So just use the former two to inline the - // downcount check here. It's better to do this near jump before the long jump to the other - // block. - FixupBranch fast_link = emit.B(CC_GT); - emit.BL(dest->checkedEntry); - emit.SetJumpTarget(fast_link); - emit.BL(dest->normalEntry); - return; - } - - // Are we able to jump directly to the normal entry? - s64 distance = ((s64)dest->normalEntry - (s64)emit.GetCodePtr()) >> 2; - if (distance >= -0x40000 && distance <= 0x3FFFF) - { - emit.B(CC_GT, dest->normalEntry); - emit.B(dest->checkedEntry); emit.BRK(101); - return; + if (emit.HasWriteFailed()) + return; } - - FixupBranch fast_link = emit.B(CC_GT); - emit.B(dest->checkedEntry); - emit.SetJumpTarget(fast_link); - emit.B(dest->normalEntry); + ASSERT(emit.GetCodePtr() == end); } void JitArm64BlockCache::WriteLinkBlock(const JitBlock::LinkData& source, const JitBlock* dest) { const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes; u8* location = source.exitPtrs; - ARM64XEmitter emit(location, location + 12); + ARM64XEmitter emit(location, location + BLOCK_LINK_SIZE); WriteLinkBlock(emit, source, dest); emit.FlushIcache(); diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.h b/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.h index 0ba537df90..dc068054d7 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.h +++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.h @@ -29,6 +29,9 @@ public: void WriteLinkBlock(Arm64Gen::ARM64XEmitter& emit, const JitBlock::LinkData& source, const JitBlock* dest = nullptr); + static constexpr size_t BLOCK_LINK_SIZE = 5 * sizeof(u32); + static constexpr size_t BLOCK_LINK_FAST_BL_OFFSET = BLOCK_LINK_SIZE - sizeof(u32); + private: void WriteLinkBlock(const JitBlock::LinkData& source, const JitBlock* dest) override; void WriteDestroyBlock(const JitBlock& block) override; From 1813f0fdb598542f3771769d388bbab76b8fc6b4 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 22 Oct 2022 14:09:03 +0200 Subject: [PATCH 004/120] Jit: Remove checkedEntry It's now always identical to normalEntry. --- .../Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp | 3 +-- Source/Core/Core/PowerPC/Jit64/Jit.cpp | 6 ++---- Source/Core/Core/PowerPC/Jit64Common/BlockCache.cpp | 9 +++------ Source/Core/Core/PowerPC/JitArm64/Jit.cpp | 6 ++---- Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp | 7 +++---- Source/Core/Core/PowerPC/JitCommon/JitCache.cpp | 4 ++-- Source/Core/Core/PowerPC/JitCommon/JitCache.h | 3 --- Source/Core/Core/PowerPC/JitInterface.cpp | 2 +- 8 files changed, 14 insertions(+), 26 deletions(-) diff --git a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp index 34f7d51593..eca1afd32d 100644 --- a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp +++ b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp @@ -311,7 +311,6 @@ void CachedInterpreter::Jit(u32 address) js.numFloatingPointInst = 0; js.curBlock = b; - b->checkedEntry = GetCodePtr(); b->normalEntry = GetCodePtr(); for (u32 i = 0; i < code_block.m_num_instructions; i++) @@ -374,7 +373,7 @@ void CachedInterpreter::Jit(u32 address) } m_code.emplace_back(); - b->codeSize = (u32)(GetCodePtr() - b->checkedEntry); + b->codeSize = static_cast(GetCodePtr() - b->normalEntry); b->originalSize = code_block.m_num_instructions; m_block_cache.FinalizeBlock(*b, jo.enableBlocklink, code_block.m_physical_addresses); diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.cpp b/Source/Core/Core/PowerPC/Jit64/Jit.cpp index 313b840132..1f4e86ffbf 100644 --- a/Source/Core/Core/PowerPC/Jit64/Jit.cpp +++ b/Source/Core/Core/PowerPC/Jit64/Jit.cpp @@ -830,9 +830,7 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC) js.numFloatingPointInst = 0; // TODO: Test if this or AlignCode16 make a difference from GetCodePtr - u8* const start = AlignCode4(); - b->checkedEntry = start; - b->normalEntry = start; + b->normalEntry = AlignCode4(); // Used to get a trace of the last few blocks before a crash, sometimes VERY useful if (m_im_here_debug) @@ -1140,7 +1138,7 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC) return false; } - b->codeSize = (u32)(GetCodePtr() - start); + b->codeSize = static_cast(GetCodePtr() - b->normalEntry); b->originalSize = code_block.m_num_instructions; #ifdef JIT_LOG_GENERATED_CODE diff --git a/Source/Core/Core/PowerPC/Jit64Common/BlockCache.cpp b/Source/Core/Core/PowerPC/Jit64Common/BlockCache.cpp index 3b0b7e5471..351c6e1c09 100644 --- a/Source/Core/Core/PowerPC/Jit64Common/BlockCache.cpp +++ b/Source/Core/Core/PowerPC/Jit64Common/BlockCache.cpp @@ -14,8 +14,7 @@ JitBlockCache::JitBlockCache(JitBase& jit) : JitBaseBlockCache{jit} void JitBlockCache::WriteLinkBlock(const JitBlock::LinkData& source, const JitBlock* dest) { u8* location = source.exitPtrs; - const u8* address = - dest ? dest->checkedEntry : m_jit.GetAsmRoutines()->dispatcher_no_timing_check; + const u8* address = dest ? dest->normalEntry : m_jit.GetAsmRoutines()->dispatcher_no_timing_check; if (source.call) { Gen::XEmitter emit(location, location + 5); @@ -42,11 +41,9 @@ void JitBlockCache::WriteLinkBlock(const JitBlock::LinkData& source, const JitBl void JitBlockCache::WriteDestroyBlock(const JitBlock& block) { - // Only clear the entry points as we might still be within this block. - Gen::XEmitter emit(block.checkedEntry, block.checkedEntry + 1); + // Only clear the entry point as we might still be within this block. + Gen::XEmitter emit(block.normalEntry, block.normalEntry + 1); emit.INT3(); - Gen::XEmitter emit2(block.normalEntry, block.normalEntry + 1); - emit2.INT3(); } void JitBlockCache::Init() diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp index f8b2a99f72..8bf5178616 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp @@ -848,9 +848,7 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC) js.numLoadStoreInst = 0; js.numFloatingPointInst = 0; - u8* const start = GetWritableCodePtr(); - b->checkedEntry = start; - b->normalEntry = start; + b->normalEntry = GetWritableCodePtr(); // Conditionally add profiling code. if (jo.profile_blocks) @@ -1117,7 +1115,7 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC) return false; } - b->codeSize = (u32)(GetCodePtr() - start); + b->codeSize = static_cast(GetCodePtr() - b->normalEntry); b->originalSize = code_block.m_num_instructions; FlushIcache(); diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp b/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp index f0e6922df8..2fcefa019c 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp @@ -96,11 +96,10 @@ void JitArm64BlockCache::WriteLinkBlock(const JitBlock::LinkData& source, const void JitArm64BlockCache::WriteDestroyBlock(const JitBlock& block) { - // Only clear the entry points as we might still be within this block. - ARM64XEmitter emit(block.checkedEntry, block.normalEntry + 4); + // Only clear the entry point as we might still be within this block. + ARM64XEmitter emit(block.normalEntry, block.normalEntry + 4); const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes; - while (emit.GetWritableCodePtr() <= block.normalEntry) - emit.BRK(0x123); + emit.BRK(0x123); emit.FlushIcache(); } diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp index 6b746d94d1..a41ebb71b2 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp @@ -163,12 +163,12 @@ void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link, if (Common::JitRegister::IsEnabled() && (symbol = g_symbolDB.GetSymbolFromAddr(block.effectiveAddress)) != nullptr) { - Common::JitRegister::Register(block.checkedEntry, block.codeSize, "JIT_PPC_{}_{:08x}", + Common::JitRegister::Register(block.normalEntry, block.codeSize, "JIT_PPC_{}_{:08x}", symbol->function_name.c_str(), block.physicalAddress); } else { - Common::JitRegister::Register(block.checkedEntry, block.codeSize, "JIT_PPC_{:08x}", + Common::JitRegister::Register(block.normalEntry, block.codeSize, "JIT_PPC_{:08x}", block.physicalAddress); } } diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.h b/Source/Core/Core/PowerPC/JitCommon/JitCache.h index 61b5966a14..fb72cbe07e 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.h @@ -30,9 +30,6 @@ struct JitBlockData u8* far_begin; u8* far_end; - // A special entry point for block linking; usually used to check the - // downcount. - u8* checkedEntry; // The normal entry point for the block, returned by Dispatch(). u8* normalEntry; diff --git a/Source/Core/Core/PowerPC/JitInterface.cpp b/Source/Core/Core/PowerPC/JitInterface.cpp index 5e369067f6..0aed99b225 100644 --- a/Source/Core/Core/PowerPC/JitInterface.cpp +++ b/Source/Core/Core/PowerPC/JitInterface.cpp @@ -187,7 +187,7 @@ JitInterface::GetHostCode(u32 address) const } GetHostCodeResult result; - result.code = block->checkedEntry; + result.code = block->normalEntry; result.code_size = block->codeSize; result.entry_address = block->effectiveAddress; return result; From d2c5d79614c728735cf637d5c53eb6591828dc79 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 22 Oct 2022 16:30:12 +0200 Subject: [PATCH 005/120] JitArm64: Use farcode in WriteLinkBlock Now block link nearcode is back to a length of three instructions. Unfortunately, the code I'm adding to Jit.cpp ends up being a bit messy because we need to handle the case of already being in farcode... --- Source/Core/Core/PowerPC/JitArm64/Jit.cpp | 75 +++++++++++++++++++ .../Core/PowerPC/JitArm64/JitArm64Cache.cpp | 15 ++-- .../Core/PowerPC/JitArm64/JitArm64Cache.h | 2 +- Source/Core/Core/PowerPC/JitCommon/JitCache.h | 3 + 4 files changed, 85 insertions(+), 10 deletions(-) diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp index 8bf5178616..7f93d359af 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp @@ -372,12 +372,29 @@ void JitArm64::WriteExit(u32 destination, bool LK, u32 exit_address_after_return STP(IndexType::Pre, ARM64Reg::X0, ARM64Reg::X1, ARM64Reg::SP, -16); } + constexpr size_t primary_farcode_size = 3 * sizeof(u32); + const bool switch_to_far_code = !IsInFarCode(); + const u8* primary_farcode_addr; + if (switch_to_far_code) + { + SwitchToFarCode(); + primary_farcode_addr = GetCodePtr(); + SwitchToNearCode(); + } + else + { + primary_farcode_addr = GetCodePtr() + JitArm64BlockCache::BLOCK_LINK_SIZE + + (LK ? JitArm64BlockCache::BLOCK_LINK_SIZE : 0); + } + const u8* return_farcode_addr = primary_farcode_addr + primary_farcode_size; + JitBlock* b = js.curBlock; JitBlock::LinkData linkData; linkData.exitAddress = destination; linkData.exitPtrs = GetWritableCodePtr(); linkData.linkStatus = false; linkData.call = LK; + linkData.exitFarcode = primary_farcode_addr; b->linkData.push_back(linkData); blocks.WriteLinkBlock(*this, linkData); @@ -391,10 +408,32 @@ void JitArm64::WriteExit(u32 destination, bool LK, u32 exit_address_after_return linkData.exitPtrs = GetWritableCodePtr(); linkData.linkStatus = false; linkData.call = false; + linkData.exitFarcode = return_farcode_addr; b->linkData.push_back(linkData); blocks.WriteLinkBlock(*this, linkData); } + + if (switch_to_far_code) + SwitchToFarCode(); + DEBUG_ASSERT(GetCodePtr() == primary_farcode_addr || HasWriteFailed()); + MOVI2R(DISPATCHER_PC, destination); + if (LK) + BL(GetAsmRoutines()->do_timing); + else + B(GetAsmRoutines()->do_timing); + + if (LK) + { + if (GetCodePtr() == return_farcode_addr - sizeof(u32)) + BRK(101); + DEBUG_ASSERT(GetCodePtr() == return_farcode_addr || HasWriteFailed()); + MOVI2R(DISPATCHER_PC, exit_address_after_return); + B(GetAsmRoutines()->do_timing); + } + + if (switch_to_far_code) + SwitchToNearCode(); } void JitArm64::WriteExit(Arm64Gen::ARM64Reg dest, bool LK, u32 exit_address_after_return) @@ -431,9 +470,27 @@ void JitArm64::WriteExit(Arm64Gen::ARM64Reg dest, bool LK, u32 exit_address_afte linkData.exitPtrs = GetWritableCodePtr(); linkData.linkStatus = false; linkData.call = false; + const bool switch_to_far_code = !IsInFarCode(); + if (switch_to_far_code) + { + SwitchToFarCode(); + linkData.exitFarcode = GetCodePtr(); + SwitchToNearCode(); + } + else + { + linkData.exitFarcode = GetCodePtr() + JitArm64BlockCache::BLOCK_LINK_SIZE; + } b->linkData.push_back(linkData); blocks.WriteLinkBlock(*this, linkData); + + if (switch_to_far_code) + SwitchToFarCode(); + MOVI2R(DISPATCHER_PC, exit_address_after_return); + B(GetAsmRoutines()->do_timing); + if (switch_to_far_code) + SwitchToNearCode(); } } @@ -465,10 +522,28 @@ void JitArm64::FakeLKExit(u32 exit_address_after_return) linkData.exitPtrs = GetWritableCodePtr(); linkData.linkStatus = false; linkData.call = false; + const bool switch_to_far_code = !IsInFarCode(); + if (switch_to_far_code) + { + SwitchToFarCode(); + linkData.exitFarcode = GetCodePtr(); + SwitchToNearCode(); + } + else + { + linkData.exitFarcode = GetCodePtr() + JitArm64BlockCache::BLOCK_LINK_SIZE; + } b->linkData.push_back(linkData); blocks.WriteLinkBlock(*this, linkData); + if (switch_to_far_code) + SwitchToFarCode(); + MOVI2R(DISPATCHER_PC, exit_address_after_return); + B(GetAsmRoutines()->do_timing); + if (switch_to_far_code) + SwitchToNearCode(); + SetJumpTarget(skip_exit); } diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp b/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp index 2fcefa019c..e991fe04c9 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp @@ -29,8 +29,9 @@ void JitArm64BlockCache::WriteLinkBlock(Arm64Gen::ARM64XEmitter& emit, emit.MOVI2R(DISPATCHER_PC, source.exitAddress); if (source.call) { - while (emit.GetCodePtr() < start + BLOCK_LINK_FAST_BL_OFFSET && !emit.HasWriteFailed()) + if (emit.GetCodePtr() == start + BLOCK_LINK_FAST_BL_OFFSET - sizeof(u32)) emit.NOP(); + DEBUG_ASSERT(emit.GetCodePtr() == start + BLOCK_LINK_FAST_BL_OFFSET || emit.HasWriteFailed()); emit.BL(m_jit.GetAsmRoutines()->dispatcher); } else @@ -45,10 +46,8 @@ void JitArm64BlockCache::WriteLinkBlock(Arm64Gen::ARM64XEmitter& emit, // The "fast" BL should be the last instruction, so that the return address matches the // address that was pushed onto the stack by the function that called WriteLinkBlock FixupBranch fast = emit.B(CC_GT); - emit.MOVI2R(DISPATCHER_PC, source.exitAddress); - emit.BL(m_jit.GetAsmRoutines()->do_timing); - while (emit.GetCodePtr() < start + BLOCK_LINK_FAST_BL_OFFSET && !emit.HasWriteFailed()) - emit.BRK(101); + emit.B(source.exitFarcode); + DEBUG_ASSERT(emit.GetCodePtr() == start + BLOCK_LINK_FAST_BL_OFFSET || emit.HasWriteFailed()); emit.SetJumpTarget(fast); emit.BL(dest->normalEntry); } @@ -59,16 +58,14 @@ void JitArm64BlockCache::WriteLinkBlock(Arm64Gen::ARM64XEmitter& emit, if (block_distance >= -0x40000 && block_distance <= 0x3FFFF) { emit.B(CC_GT, dest->normalEntry); - emit.MOVI2R(DISPATCHER_PC, source.exitAddress); - emit.B(m_jit.GetAsmRoutines()->do_timing); + emit.B(source.exitFarcode); } else { FixupBranch slow = emit.B(CC_LE); emit.B(dest->normalEntry); emit.SetJumpTarget(slow); - emit.MOVI2R(DISPATCHER_PC, source.exitAddress); - emit.B(m_jit.GetAsmRoutines()->do_timing); + emit.B(source.exitFarcode); } } } diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.h b/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.h index dc068054d7..1214e8c308 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.h +++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.h @@ -29,7 +29,7 @@ public: void WriteLinkBlock(Arm64Gen::ARM64XEmitter& emit, const JitBlock::LinkData& source, const JitBlock* dest = nullptr); - static constexpr size_t BLOCK_LINK_SIZE = 5 * sizeof(u32); + static constexpr size_t BLOCK_LINK_SIZE = 3 * sizeof(u32); static constexpr size_t BLOCK_LINK_FAST_BL_OFFSET = BLOCK_LINK_SIZE - sizeof(u32); private: diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.h b/Source/Core/Core/PowerPC/JitCommon/JitCache.h index fb72cbe07e..d3f353e815 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.h @@ -70,6 +70,9 @@ struct JitBlock : public JitBlockData struct LinkData { u8* exitPtrs; // to be able to rewrite the exit jump +#ifdef _M_ARM_64 + const u8* exitFarcode; +#endif u32 exitAddress; bool linkStatus; // is it already linked? bool call; From 262a3be08a7c556c275a857190a494ce94de3da8 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 31 Jul 2023 16:50:49 +0200 Subject: [PATCH 006/120] JitArm64: Store PC on debug exit Should fix https://bugs.dolphin-emu.org/issues/13178. Jit64 doesn't need to do this because it stores PC directly into ppcState instead of first storing it in a register. --- Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp b/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp index 03b3c7487d..9843f06d09 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp @@ -223,10 +223,16 @@ void JitArm64::GenerateAsm() // We can safely assume that downcount >= 1 B(dispatcher_no_check); + if (enable_debugging) + { + SetJumpTarget(debug_exit); + static_assert(PPCSTATE_OFF(pc) <= 252); + static_assert(PPCSTATE_OFF(pc) + 4 == PPCSTATE_OFF(npc)); + STP(IndexType::Signed, DISPATCHER_PC, DISPATCHER_PC, PPC_REG, PPCSTATE_OFF(pc)); + } + dispatcher_exit = GetCodePtr(); SetJumpTarget(exit); - if (enable_debugging) - SetJumpTarget(debug_exit); // Reset the stack pointer, since the BLR optimization may have pushed things onto the stack // without popping them. From 9ef79dd2f510a2884f735521baf98dc3bc3d597c Mon Sep 17 00:00:00 2001 From: Dentomologist Date: Sat, 12 Aug 2023 14:35:24 -0700 Subject: [PATCH 007/120] IOS: Remove unused member variable from SharedContentMap and UIDSys Resolve unused-private-field warnings on Android. --- Source/Core/Core/IOS/ES/Formats.cpp | 4 ++-- Source/Core/Core/IOS/ES/Formats.h | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/Core/Core/IOS/ES/Formats.cpp b/Source/Core/Core/IOS/ES/Formats.cpp index 6c263e98b4..4fafcf860d 100644 --- a/Source/Core/Core/IOS/ES/Formats.cpp +++ b/Source/Core/Core/IOS/ES/Formats.cpp @@ -554,7 +554,7 @@ struct SharedContentMap::Entry }; constexpr char CONTENT_MAP_PATH[] = "/shared1/content.map"; -SharedContentMap::SharedContentMap(HLE::FSCore& fs_core) : m_fs_core{fs_core}, m_fs{fs_core.GetFS()} +SharedContentMap::SharedContentMap(HLE::FSCore& fs_core) : m_fs{fs_core.GetFS()} { static_assert(sizeof(Entry) == 28, "SharedContentMap::Entry has the wrong size"); @@ -650,7 +650,7 @@ static std::pair ReadUidSysEntry(HLE::FSCore& fs, u64 fd, u64* ticks) } constexpr char UID_MAP_PATH[] = "/sys/uid.sys"; -UIDSys::UIDSys(HLE::FSCore& fs_core) : m_fs_core{fs_core}, m_fs{fs_core.GetFS()} +UIDSys::UIDSys(HLE::FSCore& fs_core) : m_fs{fs_core.GetFS()} { if (const auto fd = fs_core.Open(PID_KERNEL, PID_KERNEL, UID_MAP_PATH, HLE::FS::Mode::Read, {}, &m_ticks); diff --git a/Source/Core/Core/IOS/ES/Formats.h b/Source/Core/Core/IOS/ES/Formats.h index 56704e9caa..a3d920e759 100644 --- a/Source/Core/Core/IOS/ES/Formats.h +++ b/Source/Core/Core/IOS/ES/Formats.h @@ -296,7 +296,6 @@ private: struct Entry; u32 m_last_id = 0; std::vector m_entries; - HLE::FSCore& m_fs_core; std::shared_ptr m_fs; u64 m_ticks = 0; }; @@ -313,7 +312,6 @@ public: u64 GetTicks() const { return m_ticks; } private: - HLE::FSCore& m_fs_core; std::shared_ptr m_fs; std::map m_entries; u64 m_ticks = 0; From 71ce8bb6f00f4d1cbc1012270d6daefdbda4254d Mon Sep 17 00:00:00 2001 From: JosJuice Date: Wed, 16 Aug 2023 21:16:41 +0200 Subject: [PATCH 008/120] Don't call RunAsCPUThread in config callbacks In theory, our config system supports calling Set from any thread. But because we have config callbacks that call RunAsCPUThread, it's a lot more restricted in practice. Calling Set from any thread other than the host thread or the CPU thread is formally thread unsafe, and calling Set on the host thread while the CPU thread is showing a panic alert causes a deadlock. This is especially a problem because 04072f0 made the "Ignore for this session" button in panic alerts call Set. Because so many of our config callbacks want their code to run on the CPU thread, I thought it would make sense to have a centralized way to move execution to the CPU thread for config callbacks. To solve the deadlock problem, this new way is non-blocking. This means that threads other than the CPU thread might continue executing before the CPU thread is informed of the new config, but I don't think there's any problem with that. Intends to fix https://bugs.dolphin-emu.org/issues/13108. --- Source/Core/Common/Config/Config.h | 3 +- Source/Core/Core/CMakeLists.txt | 2 + Source/Core/Core/CPUThreadConfigCallback.cpp | 76 +++++++++++++++++++ Source/Core/Core/CPUThreadConfigCallback.h | 22 ++++++ Source/Core/Core/Core.cpp | 4 + Source/Core/Core/CoreTiming.cpp | 13 ++-- Source/Core/Core/FreeLookConfig.cpp | 4 +- Source/Core/Core/HW/CPU.cpp | 3 + Source/Core/Core/IOS/SDIO/SDIOSlot0.cpp | 19 ++--- .../Core/Core/PowerPC/JitCommon/JitBase.cpp | 8 +- Source/Core/DolphinLib.props | 2 + Source/Core/VideoCommon/VideoConfig.cpp | 25 ++++-- 12 files changed, 152 insertions(+), 29 deletions(-) create mode 100644 Source/Core/Core/CPUThreadConfigCallback.cpp create mode 100644 Source/Core/Core/CPUThreadConfigCallback.h diff --git a/Source/Core/Common/Config/Config.h b/Source/Core/Common/Config/Config.h index b448f234f9..4d770ee36e 100644 --- a/Source/Core/Common/Config/Config.h +++ b/Source/Core/Common/Config/Config.h @@ -22,7 +22,8 @@ void AddLayer(std::unique_ptr loader); std::shared_ptr GetLayer(LayerType layer); void RemoveLayer(LayerType layer); -// returns an ID that can be passed to RemoveConfigChangedCallback() +// Returns an ID that can be passed to RemoveConfigChangedCallback(). +// The callback may be called from any thread. size_t AddConfigChangedCallback(ConfigChangedCallback func); void RemoveConfigChangedCallback(size_t callback_id); void OnConfigChanged(); diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index f50185049a..40d02136c5 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -59,6 +59,8 @@ add_library(core Core.h CoreTiming.cpp CoreTiming.h + CPUThreadConfigCallback.cpp + CPUThreadConfigCallback.h Debugger/CodeTrace.cpp Debugger/CodeTrace.h Debugger/DebugInterface.h diff --git a/Source/Core/Core/CPUThreadConfigCallback.cpp b/Source/Core/Core/CPUThreadConfigCallback.cpp new file mode 100644 index 0000000000..2148630c22 --- /dev/null +++ b/Source/Core/Core/CPUThreadConfigCallback.cpp @@ -0,0 +1,76 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/CPUThreadConfigCallback.h" + +#include + +#include "Common/Assert.h" +#include "Common/Config/Config.h" +#include "Core/Core.h" + +namespace +{ +std::atomic s_should_run_callbacks = false; + +static std::vector> s_callbacks; +static size_t s_next_callback_id = 0; + +void RunCallbacks() +{ + for (const auto& callback : s_callbacks) + callback.second(); +} + +void OnConfigChanged() +{ + if (Core::IsCPUThread()) + { + s_should_run_callbacks.store(false, std::memory_order_relaxed); + RunCallbacks(); + } + else + { + s_should_run_callbacks.store(true, std::memory_order_relaxed); + } +} + +}; // namespace + +namespace CPUThreadConfigCallback +{ +size_t AddConfigChangedCallback(Config::ConfigChangedCallback func) +{ + DEBUG_ASSERT(Core::IsCPUThread()); + + static size_t s_config_changed_callback_id = Config::AddConfigChangedCallback(&OnConfigChanged); + + const size_t callback_id = s_next_callback_id; + ++s_next_callback_id; + s_callbacks.emplace_back(std::make_pair(callback_id, std::move(func))); + return callback_id; +} + +void RemoveConfigChangedCallback(size_t callback_id) +{ + DEBUG_ASSERT(Core::IsCPUThread()); + + for (auto it = s_callbacks.begin(); it != s_callbacks.end(); ++it) + { + if (it->first == callback_id) + { + s_callbacks.erase(it); + return; + } + } +} + +void CheckForConfigChanges() +{ + DEBUG_ASSERT(Core::IsCPUThread()); + + if (s_should_run_callbacks.exchange(false, std::memory_order_relaxed)) + RunCallbacks(); +} + +}; // namespace CPUThreadConfigCallback diff --git a/Source/Core/Core/CPUThreadConfigCallback.h b/Source/Core/Core/CPUThreadConfigCallback.h new file mode 100644 index 0000000000..c43e01a9a0 --- /dev/null +++ b/Source/Core/Core/CPUThreadConfigCallback.h @@ -0,0 +1,22 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/Config/Config.h" + +// This file lets you register callbacks like in Common/Config/Config.h, with the difference that +// callbacks registered here are guaranteed to run on the CPU thread. Callbacks registered here may +// run with a slight delay compared to regular config callbacks. + +namespace CPUThreadConfigCallback +{ +// returns an ID that can be passed to RemoveConfigChangedCallback() +size_t AddConfigChangedCallback(Config::ConfigChangedCallback func); + +void RemoveConfigChangedCallback(size_t callback_id); + +// Should be called regularly from the CPU thread +void CheckForConfigChanges(); + +}; // namespace CPUThreadConfigCallback diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 2865c81a9d..0fc7db59dd 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -41,6 +41,7 @@ #include "Core/AchievementManager.h" #include "Core/Boot/Boot.h" #include "Core/BootManager.h" +#include "Core/CPUThreadConfigCallback.h" #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/CoreTiming.h" @@ -511,6 +512,9 @@ static void EmuThread(std::unique_ptr boot, WindowSystemInfo wsi DeclareAsCPUThread(); s_frame_step = false; + // If settings have changed since the previous run, notify callbacks. + CPUThreadConfigCallback::CheckForConfigChanges(); + // Switch the window used for inputs to the render window. This way, the cursor position // is relative to the render window, instead of the main window. ASSERT(g_controller_interface.IsInit()); diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index 8e5d489a58..2e6f6a0968 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -16,6 +16,7 @@ #include "Common/Logging/Log.h" #include "Common/SPSCQueue.h" +#include "Core/CPUThreadConfigCallback.h" #include "Core/Config/MainSettings.h" #include "Core/Core.h" #include "Core/PowerPC/PowerPC.h" @@ -88,8 +89,8 @@ void CoreTimingManager::UnregisterAllEvents() void CoreTimingManager::Init() { - m_registered_config_callback_id = Config::AddConfigChangedCallback( - [this]() { Core::RunAsCPUThread([this]() { RefreshConfig(); }); }); + m_registered_config_callback_id = + CPUThreadConfigCallback::AddConfigChangedCallback([this]() { RefreshConfig(); }); RefreshConfig(); m_last_oc_factor = m_config_oc_factor; @@ -118,7 +119,7 @@ void CoreTimingManager::Shutdown() MoveEvents(); ClearPendingEvents(); UnregisterAllEvents(); - Config::RemoveConfigChangedCallback(m_registered_config_callback_id); + CPUThreadConfigCallback::RemoveConfigChangedCallback(m_registered_config_callback_id); } void CoreTimingManager::RefreshConfig() @@ -311,11 +312,13 @@ void CoreTimingManager::MoveEvents() void CoreTimingManager::Advance() { - auto& power_pc = m_system.GetPowerPC(); - auto& ppc_state = power_pc.GetPPCState(); + CPUThreadConfigCallback::CheckForConfigChanges(); MoveEvents(); + auto& power_pc = m_system.GetPowerPC(); + auto& ppc_state = power_pc.GetPPCState(); + int cyclesExecuted = m_globals.slice_length - DowncountToCycles(ppc_state.downcount); m_globals.global_timer += cyclesExecuted; m_last_oc_factor = m_config_oc_factor; diff --git a/Source/Core/Core/FreeLookConfig.cpp b/Source/Core/Core/FreeLookConfig.cpp index 4f9ca377a1..31a673ce85 100644 --- a/Source/Core/Core/FreeLookConfig.cpp +++ b/Source/Core/Core/FreeLookConfig.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "Core/FreeLookConfig.h" + +#include "Core/CPUThreadConfigCallback.h" #include "Core/Config/FreeLookSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -37,7 +39,7 @@ void Config::Refresh() { if (!s_has_registered_callback) { - ::Config::AddConfigChangedCallback([] { Core::RunAsCPUThread([] { s_config.Refresh(); }); }); + CPUThreadConfigCallback::AddConfigChangedCallback([] { s_config.Refresh(); }); s_has_registered_callback = true; } diff --git a/Source/Core/Core/HW/CPU.cpp b/Source/Core/Core/HW/CPU.cpp index aa90ab54d2..8a9634d527 100644 --- a/Source/Core/Core/HW/CPU.cpp +++ b/Source/Core/Core/HW/CPU.cpp @@ -10,6 +10,7 @@ #include "AudioCommon/AudioCommon.h" #include "Common/CommonTypes.h" #include "Common/Event.h" +#include "Core/CPUThreadConfigCallback.h" #include "Core/Core.h" #include "Core/Host.h" #include "Core/PowerPC/GDBStub.h" @@ -75,6 +76,7 @@ void CPUManager::Run() { m_state_cpu_cvar.wait(state_lock, [this] { return !m_state_paused_and_locked; }); ExecutePendingJobs(state_lock); + CPUThreadConfigCallback::CheckForConfigChanges(); Common::Event gdb_step_sync_event; switch (m_state) @@ -113,6 +115,7 @@ void CPUManager::Run() // Wait for step command. m_state_cpu_cvar.wait(state_lock, [this, &state_lock, &gdb_step_sync_event] { ExecutePendingJobs(state_lock); + CPUThreadConfigCallback::CheckForConfigChanges(); state_lock.unlock(); if (GDBStub::IsActive() && GDBStub::HasControl()) { diff --git a/Source/Core/Core/IOS/SDIO/SDIOSlot0.cpp b/Source/Core/Core/IOS/SDIO/SDIOSlot0.cpp index 55ee8e771b..6339ec087a 100644 --- a/Source/Core/Core/IOS/SDIO/SDIOSlot0.cpp +++ b/Source/Core/Core/IOS/SDIO/SDIOSlot0.cpp @@ -15,6 +15,7 @@ #include "Common/Logging/Log.h" #include "Common/SDCardUtil.h" +#include "Core/CPUThreadConfigCallback.h" #include "Core/Config/MainSettings.h" #include "Core/Config/SessionSettings.h" #include "Core/Core.h" @@ -32,27 +33,23 @@ SDIOSlot0Device::SDIOSlot0Device(EmulationKernel& ios, const std::string& device if (!Config::Get(Config::MAIN_ALLOW_SD_WRITES)) INFO_LOG_FMT(IOS_SD, "Writes to SD card disabled by user"); - m_config_callback_id = Config::AddConfigChangedCallback([this] { RefreshConfig(); }); + m_config_callback_id = + CPUThreadConfigCallback::AddConfigChangedCallback([this] { RefreshConfig(); }); m_sd_card_inserted = Config::Get(Config::MAIN_WII_SD_CARD); } SDIOSlot0Device::~SDIOSlot0Device() { - Config::RemoveConfigChangedCallback(m_config_callback_id); + CPUThreadConfigCallback::RemoveConfigChangedCallback(m_config_callback_id); } void SDIOSlot0Device::RefreshConfig() { - if (m_sd_card_inserted != Config::Get(Config::MAIN_WII_SD_CARD)) + const bool sd_card_inserted = Config::Get(Config::MAIN_WII_SD_CARD); + if (m_sd_card_inserted != sd_card_inserted) { - Core::RunAsCPUThread([this] { - const bool sd_card_inserted = Config::Get(Config::MAIN_WII_SD_CARD); - if (m_sd_card_inserted != sd_card_inserted) - { - m_sd_card_inserted = sd_card_inserted; - EventNotify(); - } - }); + m_sd_card_inserted = sd_card_inserted; + EventNotify(); } } diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp index 887e73a402..17d9fe1bea 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp @@ -7,6 +7,8 @@ #include "Common/CommonTypes.h" #include "Common/MemoryUtil.h" #include "Common/Thread.h" + +#include "Core/CPUThreadConfigCallback.h" #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" #include "Core/Core.h" @@ -64,14 +66,14 @@ JitBase::JitBase(Core::System& system) : m_code_buffer(code_buffer_size), m_system(system), m_ppc_state(system.GetPPCState()), m_mmu(system.GetMMU()) { - m_registered_config_callback_id = Config::AddConfigChangedCallback( - [this] { Core::RunAsCPUThread([this] { RefreshConfig(); }); }); + m_registered_config_callback_id = + CPUThreadConfigCallback::AddConfigChangedCallback([this] { RefreshConfig(); }); RefreshConfig(); } JitBase::~JitBase() { - Config::RemoveConfigChangedCallback(m_registered_config_callback_id); + CPUThreadConfigCallback::RemoveConfigChangedCallback(m_registered_config_callback_id); } void JitBase::RefreshConfig() diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 1b44f15ec4..882471aabf 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -190,6 +190,7 @@ + @@ -833,6 +834,7 @@ + diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index 42bf178047..b00976b9b4 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -9,6 +9,7 @@ #include "Common/CommonTypes.h" #include "Common/StringUtil.h" +#include "Core/CPUThreadConfigCallback.h" #include "Core/Config/GraphicsSettings.h" #include "Core/Config/MainSettings.h" #include "Core/ConfigManager.h" @@ -19,6 +20,7 @@ #include "VideoCommon/AbstractGfx.h" #include "VideoCommon/BPFunctions.h" #include "VideoCommon/DriverDetails.h" +#include "VideoCommon/Fifo.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/FreeLookCamera.h" #include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h" @@ -57,14 +59,21 @@ void VideoConfig::Refresh() { // There was a race condition between the video thread and the host thread here, if // corrections need to be made by VerifyValidity(). Briefly, the config will contain - // invalid values. Instead, pause emulation first, which will flush the video thread, - // update the config and correct it, then resume emulation, after which the video - // thread will detect the config has changed and act accordingly. - Config::AddConfigChangedCallback([]() { - Core::RunAsCPUThread([]() { - g_Config.Refresh(); - g_Config.VerifyValidity(); - }); + // invalid values. Instead, pause the video thread first, update the config and correct + // it, then resume emulation, after which the video thread will detect the config has + // changed and act accordingly. + CPUThreadConfigCallback::AddConfigChangedCallback([]() { + auto& system = Core::System::GetInstance(); + + const bool lock_gpu_thread = Core::IsRunningAndStarted(); + if (lock_gpu_thread) + system.GetFifo().PauseAndLock(system, true, false); + + g_Config.Refresh(); + g_Config.VerifyValidity(); + + if (lock_gpu_thread) + system.GetFifo().PauseAndLock(system, false, true); }); s_has_registered_callback = true; } From 2b17e89336ed09db7298ca66a7c697a4db9d3cf9 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Wed, 16 Aug 2023 22:16:50 +0200 Subject: [PATCH 009/120] Config: Don't clear callbacks on shutdown This fixes a problem that started happening in CoreTimingTest after the previous commit. CPUThreadConfigCallback registers a Config callback only once per run of the process, but CoreTimingTest calls Config::Shutdown after each test, and Config::Shutdown was clearing all callbacks, preventing the callback from running after that. --- Source/Core/Common/Config/Config.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Core/Common/Config/Config.cpp b/Source/Core/Common/Config/Config.cpp index ff3a8c6783..4db249d8eb 100644 --- a/Source/Core/Common/Config/Config.cpp +++ b/Source/Core/Common/Config/Config.cpp @@ -138,7 +138,6 @@ void Shutdown() WriteLock lock(s_layers_rw_lock); s_layers.clear(); - s_callbacks.clear(); } void ClearCurrentRunLayer() From 1104b93ee4944313d39bb7869dcba1ff21758d6d Mon Sep 17 00:00:00 2001 From: JosJuice Date: Wed, 16 Aug 2023 23:28:33 +0200 Subject: [PATCH 010/120] UnitTests: Declare as CPU thread when using CPUThreadConfigCallback This fixes a bunch of DEBUG_ASSERTs in the unit tests. --- Source/UnitTests/Core/PageFaultTest.cpp | 5 +++++ .../Core/PowerPC/Jit64Common/ConvertDoubleToSingle.cpp | 5 +++++ Source/UnitTests/Core/PowerPC/Jit64Common/Frsqrte.cpp | 5 +++++ .../Core/PowerPC/JitArm64/ConvertSingleDouble.cpp | 8 ++++++++ Source/UnitTests/Core/PowerPC/JitArm64/FPRF.cpp | 5 +++++ Source/UnitTests/Core/PowerPC/JitArm64/Fres.cpp | 5 +++++ Source/UnitTests/Core/PowerPC/JitArm64/Frsqrte.cpp | 5 +++++ 7 files changed, 38 insertions(+) diff --git a/Source/UnitTests/Core/PageFaultTest.cpp b/Source/UnitTests/Core/PageFaultTest.cpp index ab0cdd7bcc..3f693923e0 100644 --- a/Source/UnitTests/Core/PageFaultTest.cpp +++ b/Source/UnitTests/Core/PageFaultTest.cpp @@ -5,7 +5,9 @@ #include #include "Common/CommonTypes.h" +#include "Common/ScopeGuard.h" #include "Common/Timer.h" +#include "Core/Core.h" #include "Core/MemTools.h" #include "Core/PowerPC/JitCommon/JitBase.h" #include "Core/PowerPC/JitInterface.h" @@ -75,6 +77,9 @@ TEST(PageFault, PageFault) EXPECT_NE(data, nullptr); Common::WriteProtectMemory(data, PAGE_GRAN, false); + Core::DeclareAsCPUThread(); + Common::ScopeGuard cpu_thread_guard([] { Core::UndeclareAsCPUThread(); }); + auto& system = Core::System::GetInstance(); auto unique_pfjit = std::make_unique(system); auto& pfjit = *unique_pfjit; diff --git a/Source/UnitTests/Core/PowerPC/Jit64Common/ConvertDoubleToSingle.cpp b/Source/UnitTests/Core/PowerPC/Jit64Common/ConvertDoubleToSingle.cpp index bbf6445357..09c0f30169 100644 --- a/Source/UnitTests/Core/PowerPC/Jit64Common/ConvertDoubleToSingle.cpp +++ b/Source/UnitTests/Core/PowerPC/Jit64Common/ConvertDoubleToSingle.cpp @@ -5,7 +5,9 @@ #include #include "Common/CommonTypes.h" +#include "Common/ScopeGuard.h" #include "Common/x64ABI.h" +#include "Core/Core.h" #include "Core/PowerPC/Gekko.h" #include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h" #include "Core/PowerPC/Jit64/Jit.h" @@ -52,6 +54,9 @@ public: TEST(Jit64, ConvertDoubleToSingle) { + Core::DeclareAsCPUThread(); + Common::ScopeGuard cpu_thread_guard([] { Core::UndeclareAsCPUThread(); }); + TestCommonAsmRoutines routines(Core::System::GetInstance()); for (const u64 input : double_test_values) diff --git a/Source/UnitTests/Core/PowerPC/Jit64Common/Frsqrte.cpp b/Source/UnitTests/Core/PowerPC/Jit64Common/Frsqrte.cpp index d0ce6b3653..3300c07541 100644 --- a/Source/UnitTests/Core/PowerPC/Jit64Common/Frsqrte.cpp +++ b/Source/UnitTests/Core/PowerPC/Jit64Common/Frsqrte.cpp @@ -6,7 +6,9 @@ #include "Common/BitUtils.h" #include "Common/CommonTypes.h" #include "Common/FloatUtils.h" +#include "Common/ScopeGuard.h" #include "Common/x64ABI.h" +#include "Core/Core.h" #include "Core/PowerPC/Gekko.h" #include "Core/PowerPC/Jit64/Jit.h" #include "Core/PowerPC/Jit64Common/Jit64AsmCommon.h" @@ -59,6 +61,9 @@ public: TEST(Jit64, Frsqrte) { + Core::DeclareAsCPUThread(); + Common::ScopeGuard cpu_thread_guard([] { Core::UndeclareAsCPUThread(); }); + TestCommonAsmRoutines routines(Core::System::GetInstance()); UReg_FPSCR fpscr; diff --git a/Source/UnitTests/Core/PowerPC/JitArm64/ConvertSingleDouble.cpp b/Source/UnitTests/Core/PowerPC/JitArm64/ConvertSingleDouble.cpp index 0e87fddcda..588a1622ef 100644 --- a/Source/UnitTests/Core/PowerPC/JitArm64/ConvertSingleDouble.cpp +++ b/Source/UnitTests/Core/PowerPC/JitArm64/ConvertSingleDouble.cpp @@ -7,6 +7,8 @@ #include "Common/BitUtils.h" #include "Common/CommonTypes.h" #include "Common/FPURoundMode.h" +#include "Common/ScopeGuard.h" +#include "Core/Core.h" #include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h" #include "Core/PowerPC/JitArm64/Jit.h" #include "Core/System.h" @@ -120,6 +122,9 @@ private: TEST(JitArm64, ConvertDoubleToSingle) { + Core::DeclareAsCPUThread(); + Common::ScopeGuard cpu_thread_guard([] { Core::UndeclareAsCPUThread(); }); + TestConversion test(Core::System::GetInstance()); for (const u64 input : double_test_values) @@ -155,6 +160,9 @@ TEST(JitArm64, ConvertDoubleToSingle) TEST(JitArm64, ConvertSingleToDouble) { + Core::DeclareAsCPUThread(); + Common::ScopeGuard cpu_thread_guard([] { Core::UndeclareAsCPUThread(); }); + TestConversion test(Core::System::GetInstance()); for (const u32 input : single_test_values) diff --git a/Source/UnitTests/Core/PowerPC/JitArm64/FPRF.cpp b/Source/UnitTests/Core/PowerPC/JitArm64/FPRF.cpp index 285469b90f..0b7e250ae6 100644 --- a/Source/UnitTests/Core/PowerPC/JitArm64/FPRF.cpp +++ b/Source/UnitTests/Core/PowerPC/JitArm64/FPRF.cpp @@ -7,6 +7,8 @@ #include "Common/Arm64Emitter.h" #include "Common/BitUtils.h" #include "Common/CommonTypes.h" +#include "Common/ScopeGuard.h" +#include "Core/Core.h" #include "Core/PowerPC/Interpreter/Interpreter_FPUtils.h" #include "Core/PowerPC/JitArm64/Jit.h" #include "Core/PowerPC/PowerPC.h" @@ -70,6 +72,9 @@ static u32 RunUpdateFPRF(PowerPC::PowerPCState& ppc_state, const std::function Date: Wed, 16 Aug 2023 21:37:12 +0200 Subject: [PATCH 011/120] Use structs for config callback IDs This way you can't mix up regular config callback IDs and CPU thread config callback IDs. (It would be rather bad if you did!) --- Source/Core/AudioCommon/Mixer.h | 3 ++- Source/Core/Common/Config/Config.cpp | 8 ++++---- Source/Core/Common/Config/Config.h | 12 ++++++++++-- Source/Core/Core/CPUThreadConfigCallback.cpp | 13 ++++++++----- Source/Core/Core/CPUThreadConfigCallback.h | 14 +++++++++++--- Source/Core/Core/CoreTiming.h | 3 ++- Source/Core/Core/FifoPlayer/FifoPlayer.h | 3 ++- Source/Core/Core/HW/Wiimote.cpp | 2 +- Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h | 3 ++- Source/Core/Core/HW/WiimoteReal/WiimoteReal.h | 3 ++- Source/Core/Core/IOS/SDIO/SDIOSlot0.h | 3 ++- Source/Core/Core/PowerPC/JitCommon/JitBase.h | 3 ++- Source/Core/Core/PowerPC/PPCCache.h | 3 ++- .../DualShockUDPClient/DualShockUDPClient.cpp | 2 +- Source/Core/InputCommon/GCAdapter.cpp | 3 ++- Source/Core/UICommon/UICommon.cpp | 2 +- Source/Core/VideoCommon/Fifo.h | 3 ++- 17 files changed, 56 insertions(+), 27 deletions(-) diff --git a/Source/Core/AudioCommon/Mixer.h b/Source/Core/AudioCommon/Mixer.h index c27ff311c3..5a71f5e8af 100644 --- a/Source/Core/AudioCommon/Mixer.h +++ b/Source/Core/AudioCommon/Mixer.h @@ -10,6 +10,7 @@ #include "AudioCommon/SurroundDecoder.h" #include "AudioCommon/WaveFile.h" #include "Common/CommonTypes.h" +#include "Common/Config/Config.h" class PointerWrap; @@ -120,5 +121,5 @@ private: int m_config_timing_variance; bool m_config_audio_stretch; - size_t m_config_changed_callback_id; + Config::ConfigChangedCallbackID m_config_changed_callback_id; }; diff --git a/Source/Core/Common/Config/Config.cpp b/Source/Core/Common/Config/Config.cpp index 4db249d8eb..3f008fc913 100644 --- a/Source/Core/Common/Config/Config.cpp +++ b/Source/Core/Common/Config/Config.cpp @@ -16,7 +16,7 @@ namespace Config using Layers = std::map>; static Layers s_layers; -static std::vector> s_callbacks; +static std::vector> s_callbacks; static size_t s_next_callback_id = 0; static u32 s_callback_guards = 0; static std::atomic s_config_version = 0; @@ -65,15 +65,15 @@ void RemoveLayer(LayerType layer) OnConfigChanged(); } -size_t AddConfigChangedCallback(ConfigChangedCallback func) +ConfigChangedCallbackID AddConfigChangedCallback(ConfigChangedCallback func) { - const size_t callback_id = s_next_callback_id; + const ConfigChangedCallbackID callback_id{s_next_callback_id}; ++s_next_callback_id; s_callbacks.emplace_back(std::make_pair(callback_id, std::move(func))); return callback_id; } -void RemoveConfigChangedCallback(size_t callback_id) +void RemoveConfigChangedCallback(ConfigChangedCallbackID callback_id) { for (auto it = s_callbacks.begin(); it != s_callbacks.end(); ++it) { diff --git a/Source/Core/Common/Config/Config.h b/Source/Core/Common/Config/Config.h index 4d770ee36e..1303388566 100644 --- a/Source/Core/Common/Config/Config.h +++ b/Source/Core/Common/Config/Config.h @@ -15,6 +15,14 @@ namespace Config { +struct ConfigChangedCallbackID +{ + size_t id = -1; + + bool operator==(const ConfigChangedCallbackID&) const = default; + bool operator!=(const ConfigChangedCallbackID&) const = default; +}; + using ConfigChangedCallback = std::function; // Layer management @@ -24,8 +32,8 @@ void RemoveLayer(LayerType layer); // Returns an ID that can be passed to RemoveConfigChangedCallback(). // The callback may be called from any thread. -size_t AddConfigChangedCallback(ConfigChangedCallback func); -void RemoveConfigChangedCallback(size_t callback_id); +ConfigChangedCallbackID AddConfigChangedCallback(ConfigChangedCallback func); +void RemoveConfigChangedCallback(ConfigChangedCallbackID callback_id); void OnConfigChanged(); // Returns the number of times the config has changed in the current execution of the program diff --git a/Source/Core/Core/CPUThreadConfigCallback.cpp b/Source/Core/Core/CPUThreadConfigCallback.cpp index 2148630c22..857ba24189 100644 --- a/Source/Core/Core/CPUThreadConfigCallback.cpp +++ b/Source/Core/Core/CPUThreadConfigCallback.cpp @@ -13,7 +13,10 @@ namespace { std::atomic s_should_run_callbacks = false; -static std::vector> s_callbacks; +static std::vector< + std::pair> + s_callbacks; + static size_t s_next_callback_id = 0; void RunCallbacks() @@ -39,19 +42,19 @@ void OnConfigChanged() namespace CPUThreadConfigCallback { -size_t AddConfigChangedCallback(Config::ConfigChangedCallback func) +ConfigChangedCallbackID AddConfigChangedCallback(Config::ConfigChangedCallback func) { DEBUG_ASSERT(Core::IsCPUThread()); - static size_t s_config_changed_callback_id = Config::AddConfigChangedCallback(&OnConfigChanged); + static auto s_config_changed_callback_id = Config::AddConfigChangedCallback(&OnConfigChanged); - const size_t callback_id = s_next_callback_id; + const ConfigChangedCallbackID callback_id{s_next_callback_id}; ++s_next_callback_id; s_callbacks.emplace_back(std::make_pair(callback_id, std::move(func))); return callback_id; } -void RemoveConfigChangedCallback(size_t callback_id) +void RemoveConfigChangedCallback(ConfigChangedCallbackID callback_id) { DEBUG_ASSERT(Core::IsCPUThread()); diff --git a/Source/Core/Core/CPUThreadConfigCallback.h b/Source/Core/Core/CPUThreadConfigCallback.h index c43e01a9a0..404e522809 100644 --- a/Source/Core/Core/CPUThreadConfigCallback.h +++ b/Source/Core/Core/CPUThreadConfigCallback.h @@ -11,10 +11,18 @@ namespace CPUThreadConfigCallback { -// returns an ID that can be passed to RemoveConfigChangedCallback() -size_t AddConfigChangedCallback(Config::ConfigChangedCallback func); +struct ConfigChangedCallbackID +{ + size_t id = -1; -void RemoveConfigChangedCallback(size_t callback_id); + bool operator==(const ConfigChangedCallbackID&) const = default; + bool operator!=(const ConfigChangedCallbackID&) const = default; +}; + +// returns an ID that can be passed to RemoveConfigChangedCallback() +ConfigChangedCallbackID AddConfigChangedCallback(Config::ConfigChangedCallback func); + +void RemoveConfigChangedCallback(ConfigChangedCallbackID callback_id); // Should be called regularly from the CPU thread void CheckForConfigChanges(); diff --git a/Source/Core/Core/CoreTiming.h b/Source/Core/Core/CoreTiming.h index 1790dd70ee..6c60b74479 100644 --- a/Source/Core/Core/CoreTiming.h +++ b/Source/Core/Core/CoreTiming.h @@ -23,6 +23,7 @@ #include "Common/CommonTypes.h" #include "Common/SPSCQueue.h" +#include "Core/CPUThreadConfigCallback.h" class PointerWrap; @@ -182,7 +183,7 @@ private: EventType* m_ev_lost = nullptr; - size_t m_registered_config_callback_id = 0; + CPUThreadConfigCallback::ConfigChangedCallbackID m_registered_config_callback_id; float m_config_oc_factor = 0.0f; float m_config_oc_inv_factor = 0.0f; bool m_config_sync_on_skip_idle = false; diff --git a/Source/Core/Core/FifoPlayer/FifoPlayer.h b/Source/Core/Core/FifoPlayer/FifoPlayer.h index 365add85fe..b1cbd7a12d 100644 --- a/Source/Core/Core/FifoPlayer/FifoPlayer.h +++ b/Source/Core/Core/FifoPlayer/FifoPlayer.h @@ -10,6 +10,7 @@ #include #include "Common/Assert.h" +#include "Common/Config/Config.h" #include "Core/FifoPlayer/FifoDataFile.h" #include "Core/PowerPC/CPUCoreBase.h" #include "VideoCommon/CPMemory.h" @@ -189,7 +190,7 @@ private: CallbackFunc m_FileLoadedCb = nullptr; CallbackFunc m_FrameWrittenCb = nullptr; - size_t m_config_changed_callback_id; + Config::ConfigChangedCallbackID m_config_changed_callback_id; std::unique_ptr m_File; diff --git a/Source/Core/Core/HW/Wiimote.cpp b/Source/Core/Core/HW/Wiimote.cpp index 580bff8aee..13ef2139f6 100644 --- a/Source/Core/Core/HW/Wiimote.cpp +++ b/Source/Core/Core/HW/Wiimote.cpp @@ -30,7 +30,7 @@ static std::array s_last_connect_request_counter; namespace { static std::array, MAX_BBMOTES> s_wiimote_sources; -static std::optional s_config_callback_id = std::nullopt; +static std::optional s_config_callback_id = std::nullopt; WiimoteSource GetSource(unsigned int index) { diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h index eb45dce496..116e393bf2 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h @@ -9,6 +9,7 @@ #include #include "Common/Common.h" +#include "Common/Config/Config.h" #include "Core/HW/WiimoteCommon/WiimoteReport.h" @@ -343,6 +344,6 @@ private: IMUCursorState m_imu_cursor_state; - size_t m_config_changed_callback_id; + Config::ConfigChangedCallbackID m_config_changed_callback_id; }; } // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h index 39f13f0f44..eccf7bb22d 100644 --- a/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h +++ b/Source/Core/Core/HW/WiimoteReal/WiimoteReal.h @@ -11,6 +11,7 @@ #include #include "Common/Common.h" +#include "Common/Config/Config.h" #include "Common/Event.h" #include "Common/Flag.h" #include "Common/SPSCQueue.h" @@ -157,7 +158,7 @@ private: bool m_speaker_enabled_in_dolphin_config = false; int m_balance_board_dump_port = 0; - size_t m_config_changed_callback_id; + Config::ConfigChangedCallbackID m_config_changed_callback_id; }; class WiimoteScannerBackend diff --git a/Source/Core/Core/IOS/SDIO/SDIOSlot0.h b/Source/Core/Core/IOS/SDIO/SDIOSlot0.h index c096539e70..5c0dd201a7 100644 --- a/Source/Core/Core/IOS/SDIO/SDIOSlot0.h +++ b/Source/Core/Core/IOS/SDIO/SDIOSlot0.h @@ -10,6 +10,7 @@ #include "Common/CommonTypes.h" #include "Common/IOFile.h" +#include "Core/CPUThreadConfigCallback.h" #include "Core/IOS/Device.h" #include "Core/IOS/IOS.h" @@ -166,7 +167,7 @@ private: File::IOFile m_card; - size_t m_config_callback_id; + CPUThreadConfigCallback::ConfigChangedCallbackID m_config_callback_id; bool m_sd_card_inserted = false; }; } // namespace IOS::HLE diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.h b/Source/Core/Core/PowerPC/JitCommon/JitBase.h index e6488960e7..c2e41f8e52 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.h @@ -10,6 +10,7 @@ #include "Common/BitSet.h" #include "Common/CommonTypes.h" #include "Common/x64Emitter.h" +#include "Core/CPUThreadConfigCallback.h" #include "Core/ConfigManager.h" #include "Core/MachineContext.h" #include "Core/PowerPC/CPUCoreBase.h" @@ -129,7 +130,7 @@ protected: PPCAnalyst::CodeBuffer m_code_buffer; PPCAnalyst::PPCAnalyzer analyzer; - size_t m_registered_config_callback_id; + CPUThreadConfigCallback::ConfigChangedCallbackID m_registered_config_callback_id; bool bJITOff = false; bool bJITLoadStoreOff = false; bool bJITLoadStorelXzOff = false; diff --git a/Source/Core/Core/PowerPC/PPCCache.h b/Source/Core/Core/PowerPC/PPCCache.h index 8214b116af..5d5648a71e 100644 --- a/Source/Core/Core/PowerPC/PPCCache.h +++ b/Source/Core/Core/PowerPC/PPCCache.h @@ -8,6 +8,7 @@ #include #include "Common/CommonTypes.h" +#include "Common/Config/Config.h" class PointerWrap; @@ -61,7 +62,7 @@ struct Cache struct InstructionCache : public Cache { - std::optional m_config_callback_id = std::nullopt; + std::optional m_config_callback_id = std::nullopt; bool m_disable_icache = false; diff --git a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp index 4d3fa483e3..d3030a60fa 100644 --- a/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp +++ b/Source/Core/InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.cpp @@ -219,7 +219,7 @@ private: SteadyClock::time_point m_next_listports_time; std::thread m_hotplug_thread; Common::Flag m_hotplug_thread_running; - std::size_t m_config_change_callback_id; + Config::ConfigChangedCallbackID m_config_change_callback_id; }; std::unique_ptr CreateInputBackend(ControllerInterface* controller_interface) diff --git a/Source/Core/InputCommon/GCAdapter.cpp b/Source/Core/InputCommon/GCAdapter.cpp index 6334a23778..2d135970d1 100644 --- a/Source/Core/InputCommon/GCAdapter.cpp +++ b/Source/Core/InputCommon/GCAdapter.cpp @@ -23,6 +23,7 @@ #endif #include "Common/BitUtils.h" +#include "Common/Config/Config.h" #include "Common/Event.h" #include "Common/Flag.h" #include "Common/Logging/Log.h" @@ -158,7 +159,7 @@ static u8 s_endpoint_out = 0; static u64 s_last_init = 0; -static std::optional s_config_callback_id = std::nullopt; +static std::optional s_config_callback_id = std::nullopt; static bool s_is_adapter_wanted = false; static std::array s_config_rumble_enabled{}; diff --git a/Source/Core/UICommon/UICommon.cpp b/Source/Core/UICommon/UICommon.cpp index 2acd69f2c2..9b31ad5bf9 100644 --- a/Source/Core/UICommon/UICommon.cpp +++ b/Source/Core/UICommon/UICommon.cpp @@ -62,7 +62,7 @@ namespace UICommon { -static size_t s_config_changed_callback_id; +static Config::ConfigChangedCallbackID s_config_changed_callback_id; static void CreateDumpPath(std::string path) { diff --git a/Source/Core/VideoCommon/Fifo.h b/Source/Core/VideoCommon/Fifo.h index 20648562b4..2ee237c782 100644 --- a/Source/Core/VideoCommon/Fifo.h +++ b/Source/Core/VideoCommon/Fifo.h @@ -9,6 +9,7 @@ #include "Common/BlockingLoop.h" #include "Common/CommonTypes.h" +#include "Common/Config/Config.h" #include "Common/Event.h" #include "Common/Flag.h" @@ -121,7 +122,7 @@ private: bool m_syncing_suspended = false; Common::Event m_sync_wakeup_event; - std::optional m_config_callback_id = std::nullopt; + std::optional m_config_callback_id = std::nullopt; bool m_config_sync_gpu = false; int m_config_sync_gpu_max_distance = 0; int m_config_sync_gpu_min_distance = 0; From b62c25864f6d549976b94eda60763524413e896e Mon Sep 17 00:00:00 2001 From: JosJuice Date: Thu, 17 Aug 2023 09:15:09 +0200 Subject: [PATCH 012/120] CPUThreadConfigCallback: Remove some CPU thread asserts Turns out that we have two subsystems that want to register CPU thread callbacks from a different thread than the CPU thread: FreeLookConfig and VideoConfig. Both seem to happen from the host thread before the CPU thread starts, so there's no thread safety issue. But ideally, if we want to allow registering callbacks from threads other than the CPU thread, we should make registering callbacks actually thread-safe. This is an unsolved problem for the regular Config system, so I would like to leave it outside the scope of this PR. --- Source/Core/Core/CPUThreadConfigCallback.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Source/Core/Core/CPUThreadConfigCallback.cpp b/Source/Core/Core/CPUThreadConfigCallback.cpp index 857ba24189..a5b619f27a 100644 --- a/Source/Core/Core/CPUThreadConfigCallback.cpp +++ b/Source/Core/Core/CPUThreadConfigCallback.cpp @@ -44,8 +44,6 @@ namespace CPUThreadConfigCallback { ConfigChangedCallbackID AddConfigChangedCallback(Config::ConfigChangedCallback func) { - DEBUG_ASSERT(Core::IsCPUThread()); - static auto s_config_changed_callback_id = Config::AddConfigChangedCallback(&OnConfigChanged); const ConfigChangedCallbackID callback_id{s_next_callback_id}; @@ -56,8 +54,6 @@ ConfigChangedCallbackID AddConfigChangedCallback(Config::ConfigChangedCallback f void RemoveConfigChangedCallback(ConfigChangedCallbackID callback_id) { - DEBUG_ASSERT(Core::IsCPUThread()); - for (auto it = s_callbacks.begin(); it != s_callbacks.end(); ++it) { if (it->first == callback_id) From fae3aee9e0b6bc7f193bccf71a4c34a06237e4aa Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 27 Jun 2023 12:41:14 +0300 Subject: [PATCH 013/120] Video: The `% 4` that was done on the rendering resolution was only meant to be done when recording videos (due to encoding limitations) but one case was missed (this had no consequences really, as it was just in the code that automatically resizes the window). The hardcoded `4` has been replaced with `VIDEO_ENCODER_LMC` for clarity. --- Source/Core/VideoCommon/Present.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index b89c9fa32d..4d17a6519b 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -23,6 +23,9 @@ std::unique_ptr g_presenter; +// The video encoder needs the image to be a multiple of x samples. +static constexpr int VIDEO_ENCODER_LCM = 4; + namespace VideoCommon { static float AspectToWidescreen(float aspect) @@ -441,11 +444,14 @@ void Presenter::UpdateDrawRectangle() crop_width = win_width; } - // ensure divisibility by 4 to make it compatible with all the video encoders if (g_frame_dumper->IsFrameDumping()) { - draw_width = std::ceil(draw_width) - static_cast(std::ceil(draw_width)) % 4; - draw_height = std::ceil(draw_height) - static_cast(std::ceil(draw_height)) % 4; + // ensure divisibility by "VIDEO_ENCODER_LCM" to make it compatible with all the video encoders. + // Note that this is theoretically only necessary when recording videos and not screenshots. + draw_width = + std::ceil(draw_width) - static_cast(std::ceil(draw_width)) % VIDEO_ENCODER_LCM; + draw_height = + std::ceil(draw_height) - static_cast(std::ceil(draw_height)) % VIDEO_ENCODER_LCM; } m_target_rectangle.left = static_cast(std::round(win_width / 2.0 - draw_width / 2.0)); @@ -482,10 +488,13 @@ std::tuple Presenter::CalculateOutputDimensions(int width, int height) width = static_cast(std::ceil(scaled_width)); height = static_cast(std::ceil(scaled_height)); - // UpdateDrawRectangle() makes sure that the rendered image is divisible by four for video - // encoders, so do that here too to match it - width -= width % 4; - height -= height % 4; + if (g_frame_dumper->IsFrameDumping()) + { + // UpdateDrawRectangle() makes sure that the rendered image is divisible by "VIDEO_ENCODER_LCM" + // for video encoders, so do that here too to match it + width -= width % VIDEO_ENCODER_LCM; + height -= height % VIDEO_ENCODER_LCM; + } return std::make_tuple(width, height); } From cb34d1aafeca3c49412a666760c71bad434ee948 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 27 Jun 2023 12:43:45 +0300 Subject: [PATCH 014/120] Video: There was always a black line around one of the 4 edges (top/left/bottom/right) of the window because the final output size wasn't calculated right (unless the aspect ratio was set to stretch) --- Source/Core/VideoCommon/Present.cpp | 58 ++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 4d17a6519b..5229b26f74 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -33,6 +33,39 @@ static float AspectToWidescreen(float aspect) return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f)); } +static std::tuple FindClosestIntegerResolution(float width, float height, + float aspect_ratio) +{ + // We can't round both the x and y resolution as that might generate an aspect ratio + // further away from the target one, we also can't either ceil or floor both sides, + // so we find the combination or flooring and ceiling that is closest to the target ar. + const int ceiled_width = static_cast(std::ceil(width)); + const int ceiled_height = static_cast(std::ceil(height)); + const int floored_width = static_cast(std::floor(width)); + const int floored_height = static_cast(std::floor(height)); + + int int_width = floored_width; + int int_height = floored_height; + + float min_aspect_ratio_distance = std::numeric_limits::max(); + for (const int new_width : std::array{ceiled_width, floored_width}) + { + for (const int new_height : std::array{ceiled_height, floored_height}) + { + const float new_aspect_ratio = static_cast(new_width) / new_height; + const float aspect_ratio_distance = std::abs((new_aspect_ratio / aspect_ratio) - 1.f); + if (aspect_ratio_distance < min_aspect_ratio_distance) + { + min_aspect_ratio_distance = aspect_ratio_distance; + int_width = new_width; + int_height = new_height; + } + } + } + + return std::make_tuple(int_width, int_height); +} + Presenter::Presenter() { m_config_changed = @@ -414,11 +447,12 @@ void Presenter::UpdateDrawRectangle() // The rendering window size const float win_width = static_cast(m_backbuffer_width); const float win_height = static_cast(m_backbuffer_height); + const float win_aspect_ratio = win_width / win_height; // FIXME: this breaks at very low widget sizes // Make ControllerInterface aware of the render window region actually being used // to adjust mouse cursor inputs. - g_controller_interface.SetAspectRatioAdjustment(draw_aspect_ratio / (win_width / win_height)); + g_controller_interface.SetAspectRatioAdjustment(draw_aspect_ratio / win_aspect_ratio); float draw_width = draw_aspect_ratio; float draw_height = 1; @@ -427,7 +461,7 @@ void Presenter::UpdateDrawRectangle() auto [crop_width, crop_height] = ApplyStandardAspectCrop(draw_width, draw_height); // scale the picture to fit the rendering window - if (win_width / win_height >= crop_width / crop_height) + if (win_aspect_ratio >= crop_width / crop_height) { // the window is flatter than the picture draw_width *= win_height / crop_height; @@ -444,6 +478,9 @@ void Presenter::UpdateDrawRectangle() crop_width = win_width; } + int int_draw_width; + int int_draw_height; + if (g_frame_dumper->IsFrameDumping()) { // ensure divisibility by "VIDEO_ENCODER_LCM" to make it compatible with all the video encoders. @@ -452,12 +489,21 @@ void Presenter::UpdateDrawRectangle() std::ceil(draw_width) - static_cast(std::ceil(draw_width)) % VIDEO_ENCODER_LCM; draw_height = std::ceil(draw_height) - static_cast(std::ceil(draw_height)) % VIDEO_ENCODER_LCM; + int_draw_width = static_cast(draw_width); + int_draw_height = static_cast(draw_height); + } + else + { + const auto int_draw_res = + FindClosestIntegerResolution(draw_width, draw_height, win_aspect_ratio); + int_draw_width = std::get<0>(int_draw_res); + int_draw_height = std::get<1>(int_draw_res); } - m_target_rectangle.left = static_cast(std::round(win_width / 2.0 - draw_width / 2.0)); - m_target_rectangle.top = static_cast(std::round(win_height / 2.0 - draw_height / 2.0)); - m_target_rectangle.right = m_target_rectangle.left + static_cast(draw_width); - m_target_rectangle.bottom = m_target_rectangle.top + static_cast(draw_height); + m_target_rectangle.left = static_cast(std::round(win_width / 2.0 - int_draw_width / 2.0)); + m_target_rectangle.top = static_cast(std::round(win_height / 2.0 - int_draw_height / 2.0)); + m_target_rectangle.right = m_target_rectangle.left + int_draw_width; + m_target_rectangle.bottom = m_target_rectangle.top + int_draw_height; } std::tuple Presenter::ScaleToDisplayAspectRatio(const int width, From 6c7f34d5da28475a4d1eb9e67ffd1617cbd501b3 Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 27 Jun 2023 12:45:01 +0300 Subject: [PATCH 015/120] Video: The `Auto-Adjust Window Size` setting was calculating the window size based on the resolution of the window in the previous frame if we used the "stretch" aspect ratio setting, so it's result would be self influence in a loop and behave unreliably (e.g. when changing resolution between Auto/Native/2x the automatic window scaling would behave randomly) --- Source/Core/VideoCommon/Present.cpp | 60 ++++++++++++++++++++++------- Source/Core/VideoCommon/Present.h | 11 ++++-- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 5229b26f74..53d0d737d0 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -280,9 +280,12 @@ Presenter::ConvertStereoRectangle(const MathUtil::Rectangle& rc) const return std::make_tuple(left_rc, right_rc); } -float Presenter::CalculateDrawAspectRatio() const +float Presenter::CalculateDrawAspectRatio(bool allow_stretch) const { - const auto aspect_mode = g_ActiveConfig.aspect_mode; + auto aspect_mode = g_ActiveConfig.aspect_mode; + + if (!allow_stretch && aspect_mode == AspectMode::Stretch) + aspect_mode = AspectMode::Auto; // If stretch is enabled, we prefer the aspect ratio of the window. if (aspect_mode == AspectMode::Stretch) @@ -367,9 +370,15 @@ u32 Presenter::AutoIntegralScale() const u32 height = EFB_HEIGHT * m_target_rectangle.GetHeight() / m_last_xfb_height; return std::max((width - 1) / EFB_WIDTH + 1, (height - 1) / EFB_HEIGHT + 1); } + void Presenter::SetWindowSize(int width, int height) { - const auto [out_width, out_height] = g_presenter->CalculateOutputDimensions(width, height); + // While trying to guess the best window resolution, we can't allow it to use the + // "AspectMode::Stretch" setting because that would self influence the output result, + // given it would be based on the previous frame resolution + const bool allow_stretch = false; + const auto [out_width, out_height] = + g_presenter->CalculateOutputDimensions(width, height, allow_stretch); // Track the last values of width/height to avoid sending a window resize event every frame. if (out_width == m_last_window_request_width && out_height == m_last_window_request_height) @@ -377,13 +386,18 @@ void Presenter::SetWindowSize(int width, int height) m_last_window_request_width = out_width; m_last_window_request_height = out_height; + // Pass in the suggested window size. This might not always be acknowledged. Host_RequestRenderWindowSize(out_width, out_height); } // Crop to exactly 16:9 or 4:3 if enabled and not AspectMode::Stretch. -std::tuple Presenter::ApplyStandardAspectCrop(float width, float height) const +std::tuple Presenter::ApplyStandardAspectCrop(float width, float height, + bool allow_stretch) const { - const auto aspect_mode = g_ActiveConfig.aspect_mode; + auto aspect_mode = g_ActiveConfig.aspect_mode; + + if (!allow_stretch && aspect_mode == AspectMode::Stretch) + aspect_mode = AspectMode::Auto; if (!g_ActiveConfig.bCrop || aspect_mode == AspectMode::Stretch) return {width, height}; @@ -506,14 +520,14 @@ void Presenter::UpdateDrawRectangle() m_target_rectangle.bottom = m_target_rectangle.top + int_draw_height; } -std::tuple Presenter::ScaleToDisplayAspectRatio(const int width, - const int height) const +std::tuple Presenter::ScaleToDisplayAspectRatio(const int width, const int height, + bool allow_stretch) const { // Scale either the width or height depending the content aspect ratio. // This way we preserve as much resolution as possible when scaling. float scaled_width = static_cast(width); float scaled_height = static_cast(height); - const float draw_aspect = CalculateDrawAspectRatio(); + const float draw_aspect = CalculateDrawAspectRatio(allow_stretch); if (scaled_width / scaled_height >= draw_aspect) scaled_height = scaled_width / draw_aspect; else @@ -521,18 +535,38 @@ std::tuple Presenter::ScaleToDisplayAspectRatio(const int width, return std::make_tuple(scaled_width, scaled_height); } -std::tuple Presenter::CalculateOutputDimensions(int width, int height) const +std::tuple Presenter::CalculateOutputDimensions(int width, int height, + bool allow_stretch) const { width = std::max(width, 1); height = std::max(height, 1); - auto [scaled_width, scaled_height] = ScaleToDisplayAspectRatio(width, height); + auto [scaled_width, scaled_height] = ScaleToDisplayAspectRatio(width, height, allow_stretch); // Apply crop if enabled. - std::tie(scaled_width, scaled_height) = ApplyStandardAspectCrop(scaled_width, scaled_height); + std::tie(scaled_width, scaled_height) = + ApplyStandardAspectCrop(scaled_width, scaled_height, allow_stretch); - width = static_cast(std::ceil(scaled_width)); - height = static_cast(std::ceil(scaled_height)); + auto aspect_mode = g_ActiveConfig.aspect_mode; + + if (!allow_stretch && aspect_mode == AspectMode::Stretch) + aspect_mode = AspectMode::Auto; + + // Find the closest integer aspect ratio, + // this avoids a small black line from being drawn on one of the four edges + if (!g_ActiveConfig.bCrop && aspect_mode != AspectMode::Stretch) + { + const float draw_aspect_ratio = CalculateDrawAspectRatio(allow_stretch); + const auto [int_width, int_height] = + FindClosestIntegerResolution(scaled_width, scaled_height, draw_aspect_ratio); + width = int_width; + height = int_height; + } + else + { + width = static_cast(std::ceil(scaled_width)); + height = static_cast(std::ceil(scaled_height)); + } if (g_frame_dumper->IsFrameDumping()) { diff --git a/Source/Core/VideoCommon/Present.h b/Source/Core/VideoCommon/Present.h index 14d2104a65..c8bbf44257 100644 --- a/Source/Core/VideoCommon/Present.h +++ b/Source/Core/VideoCommon/Present.h @@ -58,7 +58,7 @@ public: void UpdateDrawRectangle(); - float CalculateDrawAspectRatio() const; + float CalculateDrawAspectRatio(bool allow_stretch = true) const; // Crops the target rectangle to the framebuffer dimensions, reducing the size of the source // rectangle if it is greater. Works even if the source and target rectangles don't have a @@ -103,9 +103,12 @@ private: void ProcessFrameDumping(u64 ticks) const; - std::tuple CalculateOutputDimensions(int width, int height) const; - std::tuple ApplyStandardAspectCrop(float width, float height) const; - std::tuple ScaleToDisplayAspectRatio(int width, int height) const; + std::tuple CalculateOutputDimensions(int width, int height, + bool allow_stretch = true) const; + std::tuple ApplyStandardAspectCrop(float width, float height, + bool allow_stretch = true) const; + std::tuple ScaleToDisplayAspectRatio(int width, int height, + bool allow_stretch = true) const; // Use this to convert a single target rectangle to two stereo rectangles std::tuple, MathUtil::Rectangle> From 8bca9a864fc1739aced3c91ea7dfeef3f5516eca Mon Sep 17 00:00:00 2001 From: Filoppi Date: Tue, 27 Jun 2023 12:45:19 +0300 Subject: [PATCH 016/120] Video: The `Auto` internal resolution scaling wasn't working correctly if the window weird aspect ratios (e.g. 32:9), beacuse it would account for the the portion of the image that will show black bars into the calcuations to find the best matching resolution --- Source/Core/VideoCommon/Present.cpp | 31 ++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 53d0d737d0..a190231faa 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -365,9 +365,34 @@ void* Presenter::GetNewSurfaceHandle() u32 Presenter::AutoIntegralScale() const { - // Calculate a scale based on the window size - u32 width = EFB_WIDTH * m_target_rectangle.GetWidth() / m_last_xfb_width; - u32 height = EFB_HEIGHT * m_target_rectangle.GetHeight() / m_last_xfb_height; + const float efb_aspect_ratio = static_cast(EFB_WIDTH) / EFB_HEIGHT; + const float target_aspect_ratio = + static_cast(m_target_rectangle.GetWidth()) / m_target_rectangle.GetHeight(); + + u32 target_width; + u32 target_height; + + // Instead of using the entire window (back buffer) resolution, + // find the portion of it that will actually contain the EFB output, + // and ignore the portion that will likely have black bars. + if (target_aspect_ratio >= efb_aspect_ratio) + { + target_height = m_target_rectangle.GetHeight(); + target_width = static_cast( + std::round((static_cast(m_target_rectangle.GetWidth()) / target_aspect_ratio) * + efb_aspect_ratio)); + } + else + { + target_width = m_target_rectangle.GetWidth(); + target_height = static_cast( + std::round((static_cast(m_target_rectangle.GetHeight()) * target_aspect_ratio) / + efb_aspect_ratio)); + } + + // Calculate a scale based on the adjusted window size + u32 width = EFB_WIDTH * target_width / m_last_xfb_width; + u32 height = EFB_HEIGHT * target_height / m_last_xfb_height; return std::max((width - 1) / EFB_WIDTH + 1, (height - 1) / EFB_HEIGHT + 1); } From 0f64df3e3e17e5dfc91a7fb99f3de641b1c33ae5 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 19 Aug 2023 17:14:35 +0200 Subject: [PATCH 017/120] DiscIO: Don't keep volume pointer in DiscScrubber Keeping the pointer creates use-after-free opportunities, and we don't have much reason to keep it around anyway. --- Source/Core/DiscIO/DiscScrubber.cpp | 63 +++++++++++++-------------- Source/Core/DiscIO/DiscScrubber.h | 13 +++--- Source/Core/DiscIO/ScrubbedBlob.cpp | 2 +- Source/Core/DiscIO/VolumeVerifier.cpp | 2 +- 4 files changed, 39 insertions(+), 41 deletions(-) diff --git a/Source/Core/DiscIO/DiscScrubber.cpp b/Source/Core/DiscIO/DiscScrubber.cpp index 22aa06571e..957fde32b9 100644 --- a/Source/Core/DiscIO/DiscScrubber.cpp +++ b/Source/Core/DiscIO/DiscScrubber.cpp @@ -24,13 +24,10 @@ namespace DiscIO DiscScrubber::DiscScrubber() = default; DiscScrubber::~DiscScrubber() = default; -bool DiscScrubber::SetupScrub(const Volume* disc) +bool DiscScrubber::SetupScrub(const Volume& disc) { - if (!disc) - return false; - m_disc = disc; - - m_file_size = m_disc->GetDataSize(); + m_file_size = disc.GetDataSize(); + m_has_wii_hashes = disc.HasWiiHashes(); // Round up when diving by CLUSTER_SIZE, otherwise MarkAsUsed might write out of bounds const size_t num_clusters = static_cast((m_file_size + CLUSTER_SIZE - 1) / CLUSTER_SIZE); @@ -39,7 +36,7 @@ bool DiscScrubber::SetupScrub(const Volume* disc) m_free_table.resize(num_clusters, 1); // Fill out table of free blocks - const bool success = ParseDisc(); + const bool success = ParseDisc(disc); m_is_scrubbing = success; return success; @@ -96,38 +93,40 @@ void DiscScrubber::MarkAsUsedE(u64 partition_data_offset, u64 offset, u64 size) // Compensate for 0x400 (SHA-1) per 0x8000 (cluster), and round to whole clusters u64 DiscScrubber::ToClusterOffset(u64 offset) const { - if (m_disc->HasWiiHashes()) + if (m_has_wii_hashes) return offset / 0x7c00 * CLUSTER_SIZE; else return Common::AlignDown(offset, CLUSTER_SIZE); } // Helper functions for reading the BE volume -bool DiscScrubber::ReadFromVolume(u64 offset, u32& buffer, const Partition& partition) +bool DiscScrubber::ReadFromVolume(const Volume& disc, u64 offset, u32& buffer, + const Partition& partition) { - std::optional value = m_disc->ReadSwapped(offset, partition); + std::optional value = disc.ReadSwapped(offset, partition); if (value) buffer = *value; return value.has_value(); } -bool DiscScrubber::ReadFromVolume(u64 offset, u64& buffer, const Partition& partition) +bool DiscScrubber::ReadFromVolume(const Volume& disc, u64 offset, u64& buffer, + const Partition& partition) { - std::optional value = m_disc->ReadSwappedAndShifted(offset, partition); + std::optional value = disc.ReadSwappedAndShifted(offset, partition); if (value) buffer = *value; return value.has_value(); } -bool DiscScrubber::ParseDisc() +bool DiscScrubber::ParseDisc(const Volume& disc) { - if (m_disc->GetPartitions().empty()) - return ParsePartitionData(PARTITION_NONE); + if (disc.GetPartitions().empty()) + return ParsePartitionData(disc, PARTITION_NONE); // Mark the header as used - it's mostly 0s anyways MarkAsUsed(0, 0x50000); - for (const DiscIO::Partition& partition : m_disc->GetPartitions()) + for (const DiscIO::Partition& partition : disc.GetPartitions()) { u32 tmd_size; u64 tmd_offset; @@ -136,15 +135,15 @@ bool DiscScrubber::ParseDisc() u64 h3_offset; // The H3 size is always 0x18000 - if (!ReadFromVolume(partition.offset + WII_PARTITION_TMD_SIZE_ADDRESS, tmd_size, + if (!ReadFromVolume(disc, partition.offset + WII_PARTITION_TMD_SIZE_ADDRESS, tmd_size, PARTITION_NONE) || - !ReadFromVolume(partition.offset + WII_PARTITION_TMD_OFFSET_ADDRESS, tmd_offset, + !ReadFromVolume(disc, partition.offset + WII_PARTITION_TMD_OFFSET_ADDRESS, tmd_offset, PARTITION_NONE) || - !ReadFromVolume(partition.offset + WII_PARTITION_CERT_CHAIN_SIZE_ADDRESS, cert_chain_size, - PARTITION_NONE) || - !ReadFromVolume(partition.offset + WII_PARTITION_CERT_CHAIN_OFFSET_ADDRESS, + !ReadFromVolume(disc, partition.offset + WII_PARTITION_CERT_CHAIN_SIZE_ADDRESS, + cert_chain_size, PARTITION_NONE) || + !ReadFromVolume(disc, partition.offset + WII_PARTITION_CERT_CHAIN_OFFSET_ADDRESS, cert_chain_offset, PARTITION_NONE) || - !ReadFromVolume(partition.offset + WII_PARTITION_H3_OFFSET_ADDRESS, h3_offset, + !ReadFromVolume(disc, partition.offset + WII_PARTITION_H3_OFFSET_ADDRESS, h3_offset, PARTITION_NONE)) { return false; @@ -157,7 +156,7 @@ bool DiscScrubber::ParseDisc() MarkAsUsed(partition.offset + h3_offset, WII_PARTITION_H3_SIZE); // Parse Data! This is where the big gain is - if (!ParsePartitionData(partition)) + if (!ParsePartitionData(disc, partition)) return false; } @@ -165,9 +164,9 @@ bool DiscScrubber::ParseDisc() } // Operations dealing with encrypted space are done here -bool DiscScrubber::ParsePartitionData(const Partition& partition) +bool DiscScrubber::ParsePartitionData(const Volume& disc, const Partition& partition) { - const FileSystem* filesystem = m_disc->GetFileSystem(partition); + const FileSystem* filesystem = disc.GetFileSystem(partition); if (!filesystem) { ERROR_LOG_FMT(DISCIO, "Failed to read file system for the partition at {:#x}", @@ -183,7 +182,7 @@ bool DiscScrubber::ParsePartitionData(const Partition& partition) else { u64 data_offset; - if (!ReadFromVolume(partition.offset + 0x2b8, data_offset, PARTITION_NONE)) + if (!ReadFromVolume(disc, partition.offset + 0x2b8, data_offset, PARTITION_NONE)) return false; partition_data_offset = partition.offset + data_offset; @@ -193,25 +192,25 @@ bool DiscScrubber::ParsePartitionData(const Partition& partition) // Header, Header Information, Apploader u32 apploader_size; u32 apploader_trailer_size; - if (!ReadFromVolume(0x2440 + 0x14, apploader_size, partition) || - !ReadFromVolume(0x2440 + 0x18, apploader_trailer_size, partition)) + if (!ReadFromVolume(disc, 0x2440 + 0x14, apploader_size, partition) || + !ReadFromVolume(disc, 0x2440 + 0x18, apploader_trailer_size, partition)) { return false; } MarkAsUsedE(partition_data_offset, 0, 0x2440 + apploader_size + apploader_trailer_size); // DOL - const std::optional dol_offset = GetBootDOLOffset(*m_disc, partition); + const std::optional dol_offset = GetBootDOLOffset(disc, partition); if (!dol_offset) return false; - const std::optional dol_size = GetBootDOLSize(*m_disc, partition, *dol_offset); + const std::optional dol_size = GetBootDOLSize(disc, partition, *dol_offset); if (!dol_size) return false; MarkAsUsedE(partition_data_offset, *dol_offset, *dol_size); // FST - const std::optional fst_offset = GetFSTOffset(*m_disc, partition); - const std::optional fst_size = GetFSTSize(*m_disc, partition); + const std::optional fst_offset = GetFSTOffset(disc, partition); + const std::optional fst_size = GetFSTSize(disc, partition); if (!fst_offset || !fst_size) return false; MarkAsUsedE(partition_data_offset, *fst_offset, *fst_size); diff --git a/Source/Core/DiscIO/DiscScrubber.h b/Source/Core/DiscIO/DiscScrubber.h index 72d50bf402..87528fba3b 100644 --- a/Source/Core/DiscIO/DiscScrubber.h +++ b/Source/Core/DiscIO/DiscScrubber.h @@ -29,7 +29,7 @@ public: DiscScrubber(); ~DiscScrubber(); - bool SetupScrub(const Volume* disc); + bool SetupScrub(const Volume& disc); // Returns true if the specified 32 KiB block only contains unused data bool CanBlockBeScrubbed(u64 offset) const; @@ -40,16 +40,15 @@ private: void MarkAsUsed(u64 offset, u64 size); void MarkAsUsedE(u64 partition_data_offset, u64 offset, u64 size); u64 ToClusterOffset(u64 offset) const; - bool ReadFromVolume(u64 offset, u32& buffer, const Partition& partition); - bool ReadFromVolume(u64 offset, u64& buffer, const Partition& partition); - bool ParseDisc(); - bool ParsePartitionData(const Partition& partition); + bool ReadFromVolume(const Volume& disc, u64 offset, u32& buffer, const Partition& partition); + bool ReadFromVolume(const Volume& disc, u64 offset, u64& buffer, const Partition& partition); + bool ParseDisc(const Volume& disc); + bool ParsePartitionData(const Volume& disc, const Partition& partition); void ParseFileSystemData(u64 partition_data_offset, const FileInfo& directory); - const Volume* m_disc = nullptr; - std::vector m_free_table; u64 m_file_size = 0; + bool m_has_wii_hashes = false; bool m_is_scrubbing = false; }; diff --git a/Source/Core/DiscIO/ScrubbedBlob.cpp b/Source/Core/DiscIO/ScrubbedBlob.cpp index 2f4bc689c4..84a123e4de 100644 --- a/Source/Core/DiscIO/ScrubbedBlob.cpp +++ b/Source/Core/DiscIO/ScrubbedBlob.cpp @@ -27,7 +27,7 @@ std::unique_ptr ScrubbedBlob::Create(const std::string& path) return nullptr; DiscScrubber scrubber; - if (!scrubber.SetupScrub(disc.get())) + if (!scrubber.SetupScrub(*disc)) return nullptr; std::unique_ptr blob = CreateBlobReader(path); diff --git a/Source/Core/DiscIO/VolumeVerifier.cpp b/Source/Core/DiscIO/VolumeVerifier.cpp index 66e7d00dc4..efbe129a5f 100644 --- a/Source/Core/DiscIO/VolumeVerifier.cpp +++ b/Source/Core/DiscIO/VolumeVerifier.cpp @@ -1059,7 +1059,7 @@ void VolumeVerifier::SetUpHashing() else if (m_volume.GetVolumeType() == Platform::WiiDisc) { // Set up a DiscScrubber for checking whether blocks with errors are unused - m_scrubber.SetupScrub(&m_volume); + m_scrubber.SetupScrub(m_volume); } std::sort(m_groups.begin(), m_groups.end(), From 573863703a8ea72f74f257529d53bae32535e7b0 Mon Sep 17 00:00:00 2001 From: Sepalani Date: Mon, 21 Aug 2023 22:02:44 +0400 Subject: [PATCH 018/120] MemoryViewWidget: Fix some characters being truncated --- Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp index 752c4680a0..d9ff1daac8 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp @@ -208,7 +208,7 @@ MemoryViewWidget::MemoryViewWidget(QWidget* parent) void MemoryViewWidget::UpdateFont() { const QFontMetrics fm(Settings::Instance().GetDebugFont()); - m_font_vspace = fm.lineSpacing(); + m_font_vspace = fm.lineSpacing() + 4; // BoundingRect is too unpredictable, a custom one would be needed for each view type. Different // fonts have wildly different spacing between two characters and horizontalAdvance includes // spacing. @@ -283,8 +283,8 @@ void MemoryViewWidget::CreateTable() // This sets all row heights and determines horizontal ascii spacing. // Could be placed in UpdateFont() but doesn't apply correctly unless called more. - m_table->verticalHeader()->setDefaultSectionSize(m_font_vspace - 1); - m_table->verticalHeader()->setMinimumSectionSize(m_font_vspace - 1); + m_table->verticalHeader()->setDefaultSectionSize(m_font_vspace); + m_table->verticalHeader()->setMinimumSectionSize(m_font_vspace); m_table->horizontalHeader()->setMinimumSectionSize(m_font_width * 2); const QSignalBlocker blocker(m_table); From 58ab94c30c4283e3e4e9179a5b565208143b76fb Mon Sep 17 00:00:00 2001 From: Dentomologist Date: Mon, 21 Aug 2023 11:33:24 -0700 Subject: [PATCH 019/120] GCC: Suppress PPCSTATE_OFF invalid-offsetof warnings Modify PPCSTATE_OFF and PPCSTATE_OFF_ARRAY macros when using GCC to avoid useless log spam. Specifically, use a consteval lambda with gcc _Pragma statements to disable the -Winvalid-offsetof warning inside the macros. Each successful build (and many failing ones) on the Android buildbot generates almost 300 cases of -Winvalid-offsetof, resulting in thousands of lines of log spam per build. In addition to bloating the log filesize these spurious warnings make it harder to find actual warnings. These warnings are generated by calls to the macros PPCSTATE_OFF and PPCSTATE_OFF_ARRAY, which in turn are used by many other macros used by the JIT. The ultimate cause is that offsetof is only conditionally supported on non-standard-layout types, which includes the PowerPCState struct. To address potential questions of whether there's a better way to handle this: The obvious solution would be to modify PowerPCState so that it does have a standard layout. This is unfortunately impractical. To have a standard layout a type can only contain other types with standard layouts. None of the stl containers are guaranteed to have standard layouts, and PowerPCState contains a std::tuple and std::array. PowerPCState also contains a PowerPC::Cache and InstructionCache which themselves contain std:arrays and std::vectors. Furthermore InstructionCache derives from Cache, and a derived class can only have standard layout if at most one class in its hierarchy has a non-static data member, but both classes have such members. Making InstructionCache have a standard layout would require duplicating all the functionality of Cache so it no longer derived from it, as well as replacing the stl containers. This might require having a raw pointer to said containers, with the manual memory management that implies. All of that would be much more disruptive than would be justified to get rid of some warnings (however annoying they might be). This is compounded by the fact that PowerPCState hasn't had a standard layout for a long time, if ever, and if the PPCSTATE_OFF macros weren't working reliably it would have become obvious a long time ago. As to why I picked the lambda solution over other potential changes: - Keeping the define as-is and wrapping some gcc #pragmas around it doesn't work because the pragmas don't get included when the define is substituted to the call site. - Keeping the define as a non-lambda expression and using inline _Pragma() statements would ideally be better and works fine for msvc, but fails for GCC with "'#pragma' is not allowed here". - Turning off -Winvalid-offsetof globally for gcc would work, but there might be other contexts where offsetof is problematic and GCC seems to be the only compiler warning about it. --- .../Core/PowerPC/Jit64Common/Jit64PowerPCState.h | 15 +++++++++++++++ .../Core/PowerPC/JitArm64/JitArm64_RegCache.h | 13 +++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Source/Core/Core/PowerPC/Jit64Common/Jit64PowerPCState.h b/Source/Core/Core/PowerPC/Jit64Common/Jit64PowerPCState.h index f534c853c8..692129e0ab 100644 --- a/Source/Core/Core/PowerPC/Jit64Common/Jit64PowerPCState.h +++ b/Source/Core/Core/PowerPC/Jit64Common/Jit64PowerPCState.h @@ -11,11 +11,26 @@ // We offset by 0x80 because the range of one byte memory offsets is // -0x80..0x7f. +#ifdef __GNUC__ +#define PPCSTATE_OFF(i) \ + ([]() consteval { \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Winvalid-offsetof\"") return static_cast( \ + offsetof(PowerPC::PowerPCState, i)) - \ + 0x80; \ + _Pragma("GCC diagnostic pop") \ + }()) + +#define PPCSTATE_OFF_ARRAY(elem, i) \ + (PPCSTATE_OFF(elem[0]) + static_cast(sizeof(PowerPC::PowerPCState::elem[0]) * (i))) +#else #define PPCSTATE_OFF(i) (static_cast(offsetof(PowerPC::PowerPCState, i)) - 0x80) + #define PPCSTATE_OFF_ARRAY(elem, i) \ (static_cast(offsetof(PowerPC::PowerPCState, elem[0]) + \ sizeof(PowerPC::PowerPCState::elem[0]) * (i)) - \ 0x80) +#endif #define PPCSTATE_OFF_GPR(i) PPCSTATE_OFF_ARRAY(gpr, i) #define PPCSTATE_OFF_CR(i) PPCSTATE_OFF_ARRAY(cr.fields, i) diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.h b/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.h index a8e63eb006..01339184ea 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.h +++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.h @@ -25,10 +25,23 @@ constexpr Arm64Gen::ARM64Reg PPC_REG = Arm64Gen::ARM64Reg::X29; // PC register when calling the dispatcher constexpr Arm64Gen::ARM64Reg DISPATCHER_PC = Arm64Gen::ARM64Reg::W26; +#ifdef __GNUC__ +#define PPCSTATE_OFF(elem) \ + ([]() consteval { \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Winvalid-offsetof\"") return offsetof( \ + PowerPC::PowerPCState, elem); \ + _Pragma("GCC diagnostic pop") \ + }()) + +#define PPCSTATE_OFF_ARRAY(elem, i) \ + (PPCSTATE_OFF(elem[0]) + sizeof(PowerPC::PowerPCState::elem[0]) * (i)) +#else #define PPCSTATE_OFF(elem) (offsetof(PowerPC::PowerPCState, elem)) #define PPCSTATE_OFF_ARRAY(elem, i) \ (offsetof(PowerPC::PowerPCState, elem[0]) + sizeof(PowerPC::PowerPCState::elem[0]) * (i)) +#endif #define PPCSTATE_OFF_GPR(i) PPCSTATE_OFF_ARRAY(gpr, i) #define PPCSTATE_OFF_CR(i) PPCSTATE_OFF_ARRAY(cr.fields, i) From 6e88c44d5dd041a75912f000cce631316a6b72fd Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 21 Aug 2023 16:12:49 +0200 Subject: [PATCH 020/120] Move SmallVector to Common We had one implementation of this type of data structure in Arm64Emitter and one in VideoCommon. This moves the Arm64Emitter implementation to its own file and adds begin and end functions to it, so that VideoCommon can use it. You may notice that the license header for the new file is CC0. I wrote the Arm64Emitter implementation of SmallVector, so this should be no problem. --- Source/Core/Common/Arm64Emitter.cpp | 34 +++--------------- Source/Core/Common/CMakeLists.txt | 1 + Source/Core/Common/SmallVector.h | 46 +++++++++++++++++++++++++ Source/Core/DolphinLib.props | 1 + Source/Core/VideoCommon/BPFunctions.cpp | 24 ++----------- 5 files changed, 55 insertions(+), 51 deletions(-) create mode 100644 Source/Core/Common/SmallVector.h diff --git a/Source/Core/Common/Arm64Emitter.cpp b/Source/Core/Common/Arm64Emitter.cpp index d59fd13300..296b9976c7 100644 --- a/Source/Core/Common/Arm64Emitter.cpp +++ b/Source/Core/Common/Arm64Emitter.cpp @@ -18,6 +18,7 @@ #include "Common/BitUtils.h" #include "Common/CommonTypes.h" #include "Common/MathUtil.h" +#include "Common/SmallVector.h" #ifdef _WIN32 #include @@ -1794,33 +1795,6 @@ void ARM64XEmitter::ADRP(ARM64Reg Rd, s64 imm) EncodeAddressInst(1, Rd, static_cast(imm >> 12)); } -template -class SmallVector final -{ -public: - SmallVector() = default; - explicit SmallVector(size_t size) : m_size(size) {} - - void push_back(const T& x) { m_array[m_size++] = x; } - void push_back(T&& x) { m_array[m_size++] = std::move(x); } - - template - T& emplace_back(Args&&... args) - { - return m_array[m_size++] = T{std::forward(args)...}; - } - - T& operator[](size_t i) { return m_array[i]; } - const T& operator[](size_t i) const { return m_array[i]; } - - size_t size() const { return m_size; } - bool empty() const { return m_size == 0; } - -private: - std::array m_array{}; - size_t m_size = 0; -}; - template void ARM64XEmitter::MOVI2RImpl(ARM64Reg Rd, T imm) { @@ -1844,17 +1818,17 @@ void ARM64XEmitter::MOVI2RImpl(ARM64Reg Rd, T imm) constexpr size_t max_parts = sizeof(T) / 2; - SmallVector best_parts; + Common::SmallVector best_parts; Approach best_approach; u64 best_base; - const auto instructions_required = [](const SmallVector& parts, + const auto instructions_required = [](const Common::SmallVector& parts, Approach approach) { return parts.size() + (approach > Approach::MOVNBase); }; const auto try_base = [&](T base, Approach approach, bool first_time) { - SmallVector parts; + Common::SmallVector parts; for (size_t i = 0; i < max_parts; ++i) { diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 5f3194e09d..20f657698d 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -110,6 +110,7 @@ add_library(common SettingsHandler.h SFMLHelper.cpp SFMLHelper.h + SmallVector.h SocketContext.cpp SocketContext.h SPSCQueue.h diff --git a/Source/Core/Common/SmallVector.h b/Source/Core/Common/SmallVector.h new file mode 100644 index 0000000000..09559ed21a --- /dev/null +++ b/Source/Core/Common/SmallVector.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: CC0-1.0 + +#pragma once + +#include +#include +#include + +namespace Common +{ + +// An std::vector-like container that uses no heap allocations but is limited to a maximum size. +template +class SmallVector final +{ +public: + SmallVector() = default; + explicit SmallVector(size_t size) : m_size(size) {} + + void push_back(const T& x) { m_array[m_size++] = x; } + void push_back(T&& x) { m_array[m_size++] = std::move(x); } + + template + T& emplace_back(Args&&... args) + { + return m_array[m_size++] = T{std::forward(args)...}; + } + + T& operator[](size_t i) { return m_array[i]; } + const T& operator[](size_t i) const { return m_array[i]; } + + auto begin() { return m_array.begin(); } + auto end() { return m_array.begin() + m_size; } + + auto begin() const { return m_array.begin(); } + auto end() const { return m_array.begin() + m_size; } + + size_t size() const { return m_size; } + bool empty() const { return m_size == 0; } + +private: + std::array m_array{}; + size_t m_size = 0; +}; + +} // namespace Common diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 1b44f15ec4..810bff4f26 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -143,6 +143,7 @@ + diff --git a/Source/Core/VideoCommon/BPFunctions.cpp b/Source/Core/VideoCommon/BPFunctions.cpp index ad9898b27e..6f9948a68e 100644 --- a/Source/Core/VideoCommon/BPFunctions.cpp +++ b/Source/Core/VideoCommon/BPFunctions.cpp @@ -10,6 +10,7 @@ #include "Common/Assert.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" +#include "Common/SmallVector.h" #include "VideoCommon/AbstractFramebuffer.h" #include "VideoCommon/AbstractGfx.h" @@ -76,26 +77,7 @@ bool ScissorResult::IsWorse(const ScissorRect& lhs, const ScissorRect& rhs) cons namespace { -// Dynamically sized small array of ScissorRanges (used as an heap-less alternative to std::vector -// to reduce allocation overhead) -struct RangeList -{ - static constexpr u32 MAX_RANGES = 9; - - u32 m_num_ranges = 0; - std::array m_ranges{}; - - void AddRange(int offset, int start, int end) - { - DEBUG_ASSERT(m_num_ranges < MAX_RANGES); - m_ranges[m_num_ranges] = ScissorRange(offset, start, end); - m_num_ranges++; - } - auto begin() const { return m_ranges.begin(); } - auto end() const { return m_ranges.begin() + m_num_ranges; } - - u32 size() { return m_num_ranges; } -}; +using RangeList = Common::SmallVector; static RangeList ComputeScissorRanges(int start, int end, int offset, int efb_dim) { @@ -108,7 +90,7 @@ static RangeList ComputeScissorRanges(int start, int end, int offset, int efb_di int new_end = std::clamp(end - new_off + 1, 0, efb_dim); if (new_start < new_end) { - ranges.AddRange(new_off, new_start, new_end); + ranges.emplace_back(new_off, new_start, new_end); } } From a650a16f4cf4f2e817e1101fec0310c1362dfc6d Mon Sep 17 00:00:00 2001 From: Dentomologist Date: Wed, 23 Aug 2023 10:36:52 -0700 Subject: [PATCH 021/120] JitArm64: Resolve deprecated enum conversion warning Resolve warning caused by using values from two different enums in a conditional expression which was deprecated in c++20. The warning in question is clang -Wdeprecated-anon-enum-enum-conversion and gcc -Wenum-compare. --- Source/Core/Common/FloatUtils.h | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/Source/Core/Common/FloatUtils.h b/Source/Core/Common/FloatUtils.h index b708b449e8..b1c16d172c 100644 --- a/Source/Core/Common/FloatUtils.h +++ b/Source/Core/Common/FloatUtils.h @@ -18,22 +18,16 @@ constexpr T SNANConstant() } // The most significant bit of the fraction is an is-quiet bit on all architectures we care about. -enum : u64 -{ - DOUBLE_SIGN = 0x8000000000000000ULL, - DOUBLE_EXP = 0x7FF0000000000000ULL, - DOUBLE_FRAC = 0x000FFFFFFFFFFFFFULL, - DOUBLE_ZERO = 0x0000000000000000ULL, - DOUBLE_QBIT = 0x0008000000000000ULL -}; +static constexpr u64 DOUBLE_QBIT = 0x0008000000000000ULL; +static constexpr u64 DOUBLE_SIGN = 0x8000000000000000ULL; +static constexpr u64 DOUBLE_EXP = 0x7FF0000000000000ULL; +static constexpr u64 DOUBLE_FRAC = 0x000FFFFFFFFFFFFFULL; +static constexpr u64 DOUBLE_ZERO = 0x0000000000000000ULL; -enum : u32 -{ - FLOAT_SIGN = 0x80000000, - FLOAT_EXP = 0x7F800000, - FLOAT_FRAC = 0x007FFFFF, - FLOAT_ZERO = 0x00000000 -}; +static constexpr u32 FLOAT_SIGN = 0x80000000; +static constexpr u32 FLOAT_EXP = 0x7F800000; +static constexpr u32 FLOAT_FRAC = 0x007FFFFF; +static constexpr u32 FLOAT_ZERO = 0x00000000; inline bool IsQNAN(double d) { From 7c52a524407aa8055938ec68efe135bf4162db34 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Thu, 24 Aug 2023 00:35:31 -0500 Subject: [PATCH 022/120] VideoCommon: add TextureData structure that contains the raw texture data, a sampler, and the type of texture information --- .../Core/VideoCommon/Assets/TextureAsset.cpp | 133 ++++++++++++++++++ Source/Core/VideoCommon/Assets/TextureAsset.h | 18 +++ 2 files changed, 151 insertions(+) diff --git a/Source/Core/VideoCommon/Assets/TextureAsset.cpp b/Source/Core/VideoCommon/Assets/TextureAsset.cpp index 3bf477a58a..759703a8a5 100644 --- a/Source/Core/VideoCommon/Assets/TextureAsset.cpp +++ b/Source/Core/VideoCommon/Assets/TextureAsset.cpp @@ -4,9 +4,99 @@ #include "VideoCommon/Assets/TextureAsset.h" #include "Common/Logging/Log.h" +#include "VideoCommon/BPMemory.h" namespace VideoCommon { +namespace +{ +bool ParseSampler(const VideoCommon::CustomAssetLibrary::AssetID& asset_id, + const picojson::object& json, SamplerState* sampler) +{ + if (!sampler) [[unlikely]] + return false; + + *sampler = RenderState::GetLinearSamplerState(); + + const auto sampler_state_mode_iter = json.find("texture_mode"); + if (sampler_state_mode_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'texture_mode' not found", asset_id); + return false; + } + if (!sampler_state_mode_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'texture_mode' is not the right type", + asset_id); + return false; + } + std::string sampler_state_mode = sampler_state_mode_iter->second.to_str(); + std::transform(sampler_state_mode.begin(), sampler_state_mode.end(), sampler_state_mode.begin(), + [](unsigned char c) { return std::tolower(c); }); + + if (sampler_state_mode == "clamp") + { + sampler->tm0.wrap_u = WrapMode::Clamp; + sampler->tm0.wrap_v = WrapMode::Clamp; + } + else if (sampler_state_mode == "repeat") + { + sampler->tm0.wrap_u = WrapMode::Repeat; + sampler->tm0.wrap_v = WrapMode::Repeat; + } + else if (sampler_state_mode == "mirrored_repeat") + { + sampler->tm0.wrap_u = WrapMode::Mirror; + sampler->tm0.wrap_v = WrapMode::Mirror; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, 'texture_mode' has an invalid " + "value '{}'", + asset_id, sampler_state_mode); + return false; + } + + const auto sampler_state_filter_iter = json.find("texture_filter"); + if (sampler_state_filter_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'texture_filter' not found", asset_id); + return false; + } + if (!sampler_state_filter_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, 'texture_filter' is not the right type", + asset_id); + return false; + } + std::string sampler_state_filter = sampler_state_filter_iter->second.to_str(); + std::transform(sampler_state_filter.begin(), sampler_state_filter.end(), + sampler_state_filter.begin(), [](unsigned char c) { return std::tolower(c); }); + if (sampler_state_filter == "linear") + { + sampler->tm0.min_filter = FilterMode::Linear; + sampler->tm0.mag_filter = FilterMode::Linear; + sampler->tm0.mipmap_filter = FilterMode::Linear; + } + else if (sampler_state_filter == "point") + { + sampler->tm0.min_filter = FilterMode::Linear; + sampler->tm0.mag_filter = FilterMode::Linear; + sampler->tm0.mipmap_filter = FilterMode::Linear; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, 'texture_filter' has an invalid " + "value '{}'", + asset_id, sampler_state_filter); + return false; + } + + return true; +} +} // namespace CustomAssetLibrary::LoadInfo RawTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) { auto potential_data = std::make_shared(); @@ -21,6 +111,49 @@ CustomAssetLibrary::LoadInfo RawTextureAsset::LoadImpl(const CustomAssetLibrary: return loaded_info; } +bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id, + const picojson::object& json, TextureData* data) +{ + const auto type_iter = json.find("type"); + if (type_iter == json.end()) + { + ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse json, property entry 'type' not found", + asset_id); + return false; + } + if (!type_iter->second.is()) + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, property entry 'type' is not " + "the right json type", + asset_id); + return false; + } + std::string type = type_iter->second.to_str(); + std::transform(type.begin(), type.end(), type.begin(), + [](unsigned char c) { return std::tolower(c); }); + + if (type == "texture2d") + { + data->m_type = TextureData::Type::Type_Texture2D; + } + else + { + ERROR_LOG_FMT(VIDEO, + "Asset '{}' failed to parse json, texture type '{}' " + "an invalid option", + asset_id, type); + return false; + } + + if (!ParseSampler(asset_id, json, &data->m_sampler)) + { + return false; + } + + return true; +} + CustomAssetLibrary::LoadInfo GameTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id) { auto potential_data = std::make_shared(); diff --git a/Source/Core/VideoCommon/Assets/TextureAsset.h b/Source/Core/VideoCommon/Assets/TextureAsset.h index 4cb0001a58..03b6a231d4 100644 --- a/Source/Core/VideoCommon/Assets/TextureAsset.h +++ b/Source/Core/VideoCommon/Assets/TextureAsset.h @@ -3,8 +3,11 @@ #pragma once +#include + #include "VideoCommon/Assets/CustomAsset.h" #include "VideoCommon/Assets/CustomTextureData.h" +#include "VideoCommon/RenderState.h" namespace VideoCommon { @@ -17,6 +20,21 @@ private: CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override; }; +struct TextureData +{ + static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json, + TextureData* data); + enum class Type + { + Type_Undefined, + Type_Texture2D, + Type_Max = Type_Texture2D + }; + Type m_type; + CustomTextureData m_data; + SamplerState m_sampler; +}; + class GameTextureAsset final : public CustomLoadableAsset { public: From 9223540bf49355c969e62b4b7c6d678edcf18eb5 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Thu, 24 Aug 2023 00:47:48 -0500 Subject: [PATCH 023/120] VideoCommon: additional error checking for CustomPipelineAction and move the pixel shader asset cache time back to being calculated during texture creation --- .../Runtime/Actions/CustomPipelineAction.cpp | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp index b774324a58..c26bb239b2 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp @@ -282,27 +282,8 @@ void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* dra const auto shader_data = pass.m_pixel_shader.m_asset->GetData(); if (shader_data) { - if (pass.m_pixel_shader.m_asset->GetLastLoadedTime() > pass.m_pixel_shader.m_cached_write_time) + if (m_last_generated_shader_code.GetBuffer().empty()) { - const auto material = pass.m_pixel_material.m_asset->GetData(); - if (!material) - return; - - pass.m_pixel_shader.m_cached_write_time = pass.m_pixel_shader.m_asset->GetLastLoadedTime(); - - for (const auto& prop : material->properties) - { - if (!shader_data->m_properties.contains(prop.m_code_name)) - { - ERROR_LOG_FMT(VIDEO, - "Custom pipeline has material asset '{}' that has property '{}'" - "that is not on shader asset '{}'", - pass.m_pixel_material.m_asset->GetAssetId(), prop.m_code_name, - pass.m_pixel_shader.m_asset->GetAssetId()); - return; - } - } - // Calculate shader details std::string color_shader_data = ReplaceAll(shader_data->m_shader_source, "custom_main", CUSTOM_PIXELSHADER_COLOR_FUNC); @@ -336,7 +317,6 @@ void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* dra fmt::format("{}_UNIT_{{0}}", texture_code_name)); } - m_last_generated_shader_code = ShaderCode{}; WriteDefines(&m_last_generated_shader_code, m_texture_code_names, draw_started->texture_unit); m_last_generated_shader_code.Write("{}", color_shader_data); } @@ -383,16 +363,36 @@ void CustomPipelineAction::OnTextureCreate(GraphicsModActionData::TextureCreate* if (!pass.m_pixel_shader.m_asset || pass.m_pixel_material.m_asset->GetLastLoadedTime() > pass.m_pixel_material.m_cached_write_time) { + m_last_generated_shader_code = ShaderCode{}; pass.m_pixel_shader.m_asset = loader.LoadPixelShader(material_data->shader_asset, m_library); - // Note: the asset timestamp will be updated in the draw command + pass.m_pixel_shader.m_cached_write_time = pass.m_pixel_shader.m_asset->GetLastLoadedTime(); } create->additional_dependencies->push_back(VideoCommon::CachedAsset{ pass.m_pixel_shader.m_asset, pass.m_pixel_shader.m_asset->GetLastLoadedTime()}); + const auto shader_data = pass.m_pixel_shader.m_asset->GetData(); + if (!shader_data) + { + m_valid = false; + return; + } + m_texture_code_names.clear(); std::vector> game_assets; for (const auto& property : material_data->properties) { + const auto shader_it = shader_data->m_properties.find(property.m_code_name); + if (shader_it == shader_data->m_properties.end()) + { + ERROR_LOG_FMT(VIDEO, + "Custom pipeline for texture '{}' has material asset '{}' that uses a " + "code name of '{}' but that can't be found on shader asset '{}'!", + create->texture_name, pass.m_pixel_material.m_asset->GetAssetId(), + property.m_code_name, pass.m_pixel_shader.m_asset->GetAssetId()); + m_valid = false; + return; + } + if (property.m_type == VideoCommon::MaterialProperty::Type::Type_TextureAsset) { if (property.m_value) From 0454578f45e2c0a2ddc8bc60966103844341161b Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:04:32 -0400 Subject: [PATCH 024/120] Android: Convert ControlGroupEnabledSetting to Kotlin --- .../model/ControlGroupEnabledSetting.java | 52 ------------------- .../input/model/ControlGroupEnabledSetting.kt | 25 +++++++++ 2 files changed, 25 insertions(+), 52 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.java deleted file mode 100644 index 2e22ce84aa..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.java +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model; - -import androidx.annotation.NonNull; - -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlGroup; -import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting; -import org.dolphinemu.dolphinemu.features.settings.model.Settings; - -public class ControlGroupEnabledSetting implements AbstractBooleanSetting -{ - private final ControlGroup mControlGroup; - - public ControlGroupEnabledSetting(ControlGroup controlGroup) - { - mControlGroup = controlGroup; - } - - @Override - public boolean getBoolean() - { - return mControlGroup.getEnabled(); - } - - @Override - public void setBoolean(@NonNull Settings settings, boolean newValue) - { - mControlGroup.setEnabled(newValue); - } - - @Override - public boolean isOverridden() - { - return false; - } - - @Override - public boolean isRuntimeEditable() - { - return true; - } - - @Override - public boolean delete(@NonNull Settings settings) - { - boolean newValue = mControlGroup.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_NO; - mControlGroup.setEnabled(newValue); - - return true; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.kt new file mode 100644 index 0000000000..43c983cc41 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControlGroupEnabledSetting.kt @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlGroup +import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting +import org.dolphinemu.dolphinemu.features.settings.model.Settings + +class ControlGroupEnabledSetting(private val controlGroup: ControlGroup) : AbstractBooleanSetting { + override val boolean: Boolean + get() = controlGroup.getEnabled() + + override fun setBoolean(settings: Settings, newValue: Boolean) = + controlGroup.setEnabled(newValue) + + override val isOverridden: Boolean = false + + override val isRuntimeEditable: Boolean = true + + override fun delete(settings: Settings): Boolean { + val newValue = controlGroup.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_NO + controlGroup.setEnabled(newValue) + return true + } +} From fafbb2199e24340822e7567bb913ed478b3a7949 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:05:01 -0400 Subject: [PATCH 025/120] Android: Convert Control to Kotlin --- .../{Control.java => Control.kt} | 21 ++++++------------- Source/Android/jni/AndroidCommon/IDCache.cpp | 2 +- 2 files changed, 7 insertions(+), 16 deletions(-) rename Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/{Control.java => Control.kt} (52%) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.kt similarity index 52% rename from Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.java rename to Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.kt index 6033e17e37..fcc420c740 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control.kt @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later -package org.dolphinemu.dolphinemu.features.input.model.controlleremu; +package org.dolphinemu.dolphinemu.features.input.model.controlleremu -import androidx.annotation.Keep; +import androidx.annotation.Keep /** * Represents a C++ ControllerEmu::Control. @@ -10,18 +10,9 @@ import androidx.annotation.Keep; * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed * in C++ is undefined behavior! */ -public class Control -{ - @Keep - private final long mPointer; +@Keep +class Control private constructor(private val pointer: Long) { + external fun getUiName(): String - @Keep - private Control(long pointer) - { - mPointer = pointer; - } - - public native String getUiName(); - - public native ControlReference getControlReference(); + external fun getControlReference(): ControlReference } diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index e4cacaa462..5e9a1692d5 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -703,7 +703,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) const jclass control_class = env->FindClass("org/dolphinemu/dolphinemu/features/input/model/controlleremu/Control"); s_control_class = reinterpret_cast(env->NewGlobalRef(control_class)); - s_control_pointer = env->GetFieldID(control_class, "mPointer", "J"); + s_control_pointer = env->GetFieldID(control_class, "pointer", "J"); s_control_constructor = env->GetMethodID(control_class, "", "(J)V"); env->DeleteLocalRef(control_class); From 7c79ff01002e7743fe47026b30d6131786c2f492 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:05:18 -0400 Subject: [PATCH 026/120] Android: Convert ControlGroup to Kotlin --- .../model/controlleremu/ControlGroup.java | 66 ------------------- .../input/model/controlleremu/ControlGroup.kt | 59 +++++++++++++++++ .../settings/ui/SettingsFragmentPresenter.kt | 12 ++-- Source/Android/jni/AndroidCommon/IDCache.cpp | 2 +- 4 files changed, 66 insertions(+), 73 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.java deleted file mode 100644 index 1bafd4a1f2..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.java +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model.controlleremu; - -import androidx.annotation.Keep; - -/** - * Represents a C++ ControllerEmu::ControlGroup. - * - * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed - * in C++ is undefined behavior! - */ -public class ControlGroup -{ - public static final int TYPE_OTHER = 0; - public static final int TYPE_STICK = 1; - public static final int TYPE_MIXED_TRIGGERS = 2; - public static final int TYPE_BUTTONS = 3; - public static final int TYPE_FORCE = 4; - public static final int TYPE_ATTACHMENTS = 5; - public static final int TYPE_TILT = 6; - public static final int TYPE_CURSOR = 7; - public static final int TYPE_TRIGGERS = 8; - public static final int TYPE_SLIDER = 9; - public static final int TYPE_SHAKE = 10; - public static final int TYPE_IMU_ACCELEROMETER = 11; - public static final int TYPE_IMU_GYROSCOPE = 12; - public static final int TYPE_IMU_CURSOR = 13; - - public static final int DEFAULT_ENABLED_ALWAYS = 0; - public static final int DEFAULT_ENABLED_YES = 1; - public static final int DEFAULT_ENABLED_NO = 2; - - @Keep - private final long mPointer; - - @Keep - private ControlGroup(long pointer) - { - mPointer = pointer; - } - - public native String getUiName(); - - public native int getGroupType(); - - public native int getDefaultEnabledValue(); - - public native boolean getEnabled(); - - public native void setEnabled(boolean value); - - public native int getControlCount(); - - public native Control getControl(int i); - - public native int getNumericSettingCount(); - - public native NumericSetting getNumericSetting(int i); - - /** - * If getGroupType returns TYPE_ATTACHMENTS, this returns the attachment selection setting. - * Otherwise, undefined behavior! - */ - public native NumericSetting getAttachmentSetting(); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.kt new file mode 100644 index 0000000000..9c5132b1a2 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup.kt @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.controlleremu + +import androidx.annotation.Keep + +/** + * Represents a C++ ControllerEmu::ControlGroup. + * + * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed + * in C++ is undefined behavior! + */ +@Keep +class ControlGroup private constructor(private val pointer: Long) { + external fun getUiName(): String + + external fun getGroupType(): Int + + external fun getDefaultEnabledValue(): Int + + external fun getEnabled(): Boolean + + external fun setEnabled(value: Boolean) + + external fun getControlCount(): Int + + external fun getControl(i: Int): Control + + external fun getNumericSettingCount(): Int + + external fun getNumericSetting(i: Int): NumericSetting + + /** + * If getGroupType returns TYPE_ATTACHMENTS, this returns the attachment selection setting. + * Otherwise, undefined behavior! + */ + external fun getAttachmentSetting(): NumericSetting + + companion object { + const val TYPE_OTHER = 0 + const val TYPE_STICK = 1 + const val TYPE_MIXED_TRIGGERS = 2 + const val TYPE_BUTTONS = 3 + const val TYPE_FORCE = 4 + const val TYPE_ATTACHMENTS = 5 + const val TYPE_TILT = 6 + const val TYPE_CURSOR = 7 + const val TYPE_TRIGGERS = 8 + const val TYPE_SLIDER = 9 + const val TYPE_SHAKE = 10 + const val TYPE_IMU_ACCELEROMETER = 11 + const val TYPE_IMU_GYROSCOPE = 12 + const val TYPE_IMU_CURSOR = 13 + + const val DEFAULT_ENABLED_ALWAYS = 0 + const val DEFAULT_ENABLED_YES = 1 + const val DEFAULT_ENABLED_NO = 2 + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 59858193df..09298d0b8a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -2254,12 +2254,12 @@ class SettingsFragmentPresenter( val groupCount = controller.groupCount for (i in 0 until groupCount) { val group = controller.getGroup(i) - val groupType = group.groupType + val groupType = group.getGroupType() if (groupTypeFilter != null && !groupTypeFilter.contains(groupType)) continue - sl.add(HeaderSetting(group.uiName, "")) + sl.add(HeaderSetting(group.getUiName(), "")) - if (group.defaultEnabledValue != ControlGroup.DEFAULT_ENABLED_ALWAYS) { + if (group.getDefaultEnabledValue() != ControlGroup.DEFAULT_ENABLED_ALWAYS) { sl.add( SwitchSetting( context, @@ -2270,13 +2270,13 @@ class SettingsFragmentPresenter( ) } - val controlCount = group.controlCount + val controlCount = group.getControlCount() for (j in 0 until controlCount) { sl.add(InputMappingControlSetting(group.getControl(j), controller)) } if (groupType == ControlGroup.TYPE_ATTACHMENTS) { - val attachmentSetting = group.attachmentSetting + val attachmentSetting = group.getAttachmentSetting() sl.add( SingleChoiceSetting( context, InputMappingIntSetting(attachmentSetting), @@ -2287,7 +2287,7 @@ class SettingsFragmentPresenter( ) } - val numericSettingCount = group.numericSettingCount + val numericSettingCount = group.getNumericSettingCount() for (j in 0 until numericSettingCount) { addNumericSetting(sl, group.getNumericSetting(j)) } diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 5e9a1692d5..a9bcf93b9c 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -710,7 +710,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) const jclass control_group_class = env->FindClass("org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlGroup"); s_control_group_class = reinterpret_cast(env->NewGlobalRef(control_group_class)); - s_control_group_pointer = env->GetFieldID(control_group_class, "mPointer", "J"); + s_control_group_pointer = env->GetFieldID(control_group_class, "pointer", "J"); s_control_group_constructor = env->GetMethodID(control_group_class, "", "(J)V"); env->DeleteLocalRef(control_group_class); From 4ce069cf4fabdb5ea2b36c027227e9936a07b32d Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:05:42 -0400 Subject: [PATCH 027/120] Android: Convert ControlReference to Kotlin --- .../model/controlleremu/ControlReference.java | 39 ------------------- .../model/controlleremu/ControlReference.kt | 28 +++++++++++++ Source/Android/jni/AndroidCommon/IDCache.cpp | 2 +- 3 files changed, 29 insertions(+), 40 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.java deleted file mode 100644 index 66fd9fa02d..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.java +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model.controlleremu; - -import androidx.annotation.Keep; -import androidx.annotation.Nullable; - -/** - * Represents a C++ ControlReference. - * - * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed - * in C++ is undefined behavior! - */ -public class ControlReference -{ - @Keep - private final long mPointer; - - @Keep - private ControlReference(long pointer) - { - mPointer = pointer; - } - - public native double getState(); - - public native String getExpression(); - - /** - * Sets the expression for this control reference. - * - * @param expr The new expression - * @return null on success, a human-readable error on failure - */ - @Nullable - public native String setExpression(String expr); - - public native boolean isInput(); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.kt new file mode 100644 index 0000000000..cd0c7848a6 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference.kt @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.controlleremu + +import androidx.annotation.Keep + +/** + * Represents a C++ ControlReference. + * + * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed + * in C++ is undefined behavior! + */ +@Keep +class ControlReference private constructor(private val pointer: Long) { + external fun getState(): Double + + external fun getExpression(): String + + /** + * Sets the expression for this control reference. + * + * @param expr The new expression + * @return null on success, a human-readable error on failure + */ + external fun setExpression(expr: String): String? + + external fun isInput(): Boolean +} diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index a9bcf93b9c..60c3933dc4 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -717,7 +717,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) const jclass control_reference_class = env->FindClass( "org/dolphinemu/dolphinemu/features/input/model/controlleremu/ControlReference"); s_control_reference_class = reinterpret_cast(env->NewGlobalRef(control_reference_class)); - s_control_reference_pointer = env->GetFieldID(control_reference_class, "mPointer", "J"); + s_control_reference_pointer = env->GetFieldID(control_reference_class, "pointer", "J"); s_control_reference_constructor = env->GetMethodID(control_reference_class, "", "(J)V"); env->DeleteLocalRef(control_reference_class); From 3011c0dc64d3de8bc56b8bb73fb2addae2e6c484 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:06:00 -0400 Subject: [PATCH 028/120] Android: Convert EmulatedController to Kotlin --- .../controlleremu/EmulatedController.java | 52 ------------------- .../model/controlleremu/EmulatedController.kt | 52 +++++++++++++++++++ .../features/settings/ui/SettingsAdapter.kt | 2 +- .../settings/ui/SettingsFragmentPresenter.kt | 4 +- Source/Android/jni/AndroidCommon/IDCache.cpp | 2 +- 5 files changed, 56 insertions(+), 56 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java deleted file mode 100644 index 39645ffbba..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.java +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model.controlleremu; - -import androidx.annotation.Keep; - -/** - * Represents a C++ ControllerEmu::EmulatedController. - * - * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed - * in C++ is undefined behavior! - */ -public class EmulatedController -{ - @Keep - private final long mPointer; - - @Keep - private EmulatedController(long pointer) - { - mPointer = pointer; - } - - public native String getDefaultDevice(); - - public native void setDefaultDevice(String device); - - public native int getGroupCount(); - - public native ControlGroup getGroup(int index); - - public native void updateSingleControlReference(ControlReference controlReference); - - public native void loadDefaultSettings(); - - public native void clearSettings(); - - public native void loadProfile(String path); - - public native void saveProfile(String path); - - public static native EmulatedController getGcPad(int controllerIndex); - - public static native EmulatedController getWiimote(int controllerIndex); - - public static native EmulatedController getWiimoteAttachment(int controllerIndex, - int attachmentIndex); - - public static native int getSelectedWiimoteAttachment(int controllerIndex); - - public static native NumericSetting getSidewaysWiimoteSetting(int controllerIndex); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.kt new file mode 100644 index 0000000000..348e75b6a0 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController.kt @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.controlleremu + +import androidx.annotation.Keep + +/** + * Represents a C++ ControllerEmu::EmulatedController. + * + * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed + * in C++ is undefined behavior! + */ +@Keep +class EmulatedController private constructor(private val pointer: Long) { + external fun getDefaultDevice(): String + + external fun setDefaultDevice(device: String) + + external fun getGroupCount(): Int + + external fun getGroup(index: Int): ControlGroup + + external fun updateSingleControlReference(controlReference: ControlReference) + + external fun loadDefaultSettings() + + external fun clearSettings() + + external fun loadProfile(path: String) + + external fun saveProfile(path: String) + + companion object { + @JvmStatic + external fun getGcPad(controllerIndex: Int): EmulatedController + + @JvmStatic + external fun getWiimote(controllerIndex: Int): EmulatedController + + @JvmStatic + external fun getWiimoteAttachment( + controllerIndex: Int, + attachmentIndex: Int + ): EmulatedController + + @JvmStatic + external fun getSelectedWiimoteAttachment(controllerIndex: Int): Int + + @JvmStatic + external fun getSidewaysWiimoteSetting(controllerIndex: Int): NumericSetting + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt index d5df7c0e09..e3e745fafa 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt @@ -258,7 +258,7 @@ class SettingsAdapter( } fun onInputMappingClick(item: InputMappingControlSetting, position: Int) { - if (item.controller.defaultDevice.isEmpty() && !fragmentView.isMappingAllDevices) { + if (item.controller.getDefaultDevice().isEmpty() && !fragmentView.isMappingAllDevices) { MaterialAlertDialogBuilder(fragmentView.fragmentActivity) .setMessage(R.string.input_binding_no_device) .setPositiveButton(R.string.ok, this) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 09298d0b8a..8cb63c420e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -2251,7 +2251,7 @@ class SettingsFragmentPresenter( ) { updateOldControllerSettingsWarningVisibility(controller) - val groupCount = controller.groupCount + val groupCount = controller.getGroupCount() for (i in 0 until groupCount) { val group = controller.getGroup(i) val groupType = group.getGroupType() @@ -2322,7 +2322,7 @@ class SettingsFragmentPresenter( } private fun updateOldControllerSettingsWarningVisibility(controller: EmulatedController) { - val defaultDevice = controller.defaultDevice + val defaultDevice = controller.getDefaultDevice() hasOldControllerSettings = defaultDevice.startsWith("Android/") && defaultDevice.endsWith("/Touchscreen") diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 60c3933dc4..b5c91be10c 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -725,7 +725,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) "org/dolphinemu/dolphinemu/features/input/model/controlleremu/EmulatedController"); s_emulated_controller_class = reinterpret_cast(env->NewGlobalRef(emulated_controller_class)); - s_emulated_controller_pointer = env->GetFieldID(emulated_controller_class, "mPointer", "J"); + s_emulated_controller_pointer = env->GetFieldID(emulated_controller_class, "pointer", "J"); s_emulated_controller_constructor = env->GetMethodID(emulated_controller_class, "", "(J)V"); env->DeleteLocalRef(emulated_controller_class); From 82298dc408b315467ac3e035e158895e64465fa1 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:06:17 -0400 Subject: [PATCH 029/120] Android: Convert CoreDevice to Kotlin --- .../features/input/model/CoreDevice.java | 48 ------------------- .../features/input/model/CoreDevice.kt | 28 +++++++++++ Source/Android/jni/AndroidCommon/IDCache.cpp | 4 +- 3 files changed, 30 insertions(+), 50 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.java deleted file mode 100644 index b69e9a0236..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.java +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model; - -import androidx.annotation.Keep; - -/** - * Represents a C++ ciface::Core::Device. - */ -public final class CoreDevice -{ - /** - * Represents a C++ ciface::Core::Device::Control. - * - * This class is non-static to ensure that the CoreDevice parent does not get garbage collected - * while a Control is still accessible. (CoreDevice's finalizer may delete the native controls.) - */ - @SuppressWarnings("InnerClassMayBeStatic") - public final class Control - { - @Keep - private final long mPointer; - - @Keep - private Control(long pointer) - { - mPointer = pointer; - } - - public native String getName(); - } - - @Keep - private final long mPointer; - - @Keep - private CoreDevice(long pointer) - { - mPointer = pointer; - } - - @Override - protected native void finalize(); - - public native Control[] getInputs(); - - public native Control[] getOutputs(); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.kt new file mode 100644 index 0000000000..1483422c50 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/CoreDevice.kt @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import androidx.annotation.Keep + +/** + * Represents a C++ ciface::Core::Device. + */ +@Keep +class CoreDevice private constructor(private val pointer: Long) { + /** + * Represents a C++ ciface::Core::Device::Control. + * + * This class is marked inner to ensure that the CoreDevice parent does not get garbage collected + * while a Control is still accessible. (CoreDevice's finalizer may delete the native controls.) + */ + @Keep + inner class Control private constructor(private val pointer: Long) { + external fun getName(): String + } + + protected external fun finalize() + + external fun getInputs(): Array + + external fun getOutputs(): Array +} diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index b5c91be10c..67b6af4d71 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -739,7 +739,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) const jclass core_device_class = env->FindClass("org/dolphinemu/dolphinemu/features/input/model/CoreDevice"); s_core_device_class = reinterpret_cast(env->NewGlobalRef(core_device_class)); - s_core_device_pointer = env->GetFieldID(core_device_class, "mPointer", "J"); + s_core_device_pointer = env->GetFieldID(core_device_class, "pointer", "J"); s_core_device_constructor = env->GetMethodID(core_device_class, "", "(J)V"); env->DeleteLocalRef(core_device_class); @@ -747,7 +747,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) env->FindClass("org/dolphinemu/dolphinemu/features/input/model/CoreDevice$Control"); s_core_device_control_class = reinterpret_cast(env->NewGlobalRef(core_device_control_class)); - s_core_device_control_pointer = env->GetFieldID(core_device_control_class, "mPointer", "J"); + s_core_device_control_pointer = env->GetFieldID(core_device_control_class, "pointer", "J"); s_core_device_control_constructor = env->GetMethodID(core_device_control_class, "", "(Lorg/dolphinemu/dolphinemu/features/input/model/CoreDevice;J)V"); From 9ac1847cbd08024b2da8e201b2d5c03cc94595c6 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:08:08 -0400 Subject: [PATCH 030/120] Android: Convert NumericSetting to Kotlin --- .../model/controlleremu/NumericSetting.java | 104 ------------------ .../model/controlleremu/NumericSetting.kt | 97 ++++++++++++++++ .../settings/ui/SettingsFragmentPresenter.kt | 14 +-- Source/Android/jni/AndroidCommon/IDCache.cpp | 2 +- 4 files changed, 105 insertions(+), 112 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.java deleted file mode 100644 index bcb9dce181..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.java +++ /dev/null @@ -1,104 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model.controlleremu; - -import androidx.annotation.Keep; - -/** - * Represents a C++ ControllerEmu::NumericSetting. - * - * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed - * in C++ is undefined behavior! - */ -public class NumericSetting -{ - public static final int TYPE_INT = 0; - public static final int TYPE_DOUBLE = 1; - public static final int TYPE_BOOLEAN = 2; - - @Keep - private final long mPointer; - - @Keep - private NumericSetting(long pointer) - { - mPointer = pointer; - } - - /** - * @return The name used in the UI. - */ - public native String getUiName(); - - /** - * @return A string applied to the number in the UI (unit of measure). - */ - public native String getUiSuffix(); - - /** - * @return Detailed description of the setting. - */ - public native String getUiDescription(); - - /** - * @return TYPE_INT, TYPE_DOUBLE or TYPE_BOOLEAN - */ - public native int getType(); - - public native ControlReference getControlReference(); - - /** - * If the type is TYPE_INT, gets the current value. Otherwise, undefined behavior! - */ - public native int getIntValue(); - - /** - * If the type is TYPE_INT, sets the current value. Otherwise, undefined behavior! - */ - public native void setIntValue(int value); - - /** - * If the type is TYPE_INT, gets the default value. Otherwise, undefined behavior! - */ - public native int getIntDefaultValue(); - - /** - * If the type is TYPE_DOUBLE, gets the current value. Otherwise, undefined behavior! - */ - public native double getDoubleValue(); - - /** - * If the type is TYPE_DOUBLE, sets the current value. Otherwise, undefined behavior! - */ - public native void setDoubleValue(double value); - - /** - * If the type is TYPE_DOUBLE, gets the default value. Otherwise, undefined behavior! - */ - public native double getDoubleDefaultValue(); - - /** - * If the type is TYPE_DOUBLE, returns the minimum valid value. Otherwise, undefined behavior! - */ - public native double getDoubleMin(); - - /** - * If the type is TYPE_DOUBLE, returns the maximum valid value. Otherwise, undefined behavior! - */ - public native double getDoubleMax(); - - /** - * If the type is TYPE_BOOLEAN, gets the current value. Otherwise, undefined behavior! - */ - public native boolean getBooleanValue(); - - /** - * If the type is TYPE_BOOLEAN, sets the current value. Otherwise, undefined behavior! - */ - public native void setBooleanValue(boolean value); - - /** - * If the type is TYPE_BOOLEAN, gets the default value. Otherwise, undefined behavior! - */ - public native boolean getBooleanDefaultValue(); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.kt new file mode 100644 index 0000000000..0a2485deab --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting.kt @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.controlleremu + +import androidx.annotation.Keep + +/** + * Represents a C++ ControllerEmu::NumericSetting. + * + * The lifetime of this class is managed by C++ code. Calling methods on it after it's destroyed + * in C++ is undefined behavior! + */ +@Keep +class NumericSetting private constructor(private val pointer: Long) { + /** + * @return The name used in the UI. + */ + external fun getUiName(): String + + /** + * @return A string applied to the number in the UI (unit of measure). + */ + external fun getUiSuffix(): String + + /** + * @return Detailed description of the setting. + */ + external fun getUiDescription(): String + + /** + * @return TYPE_INT, TYPE_DOUBLE or TYPE_BOOLEAN + */ + external fun getType(): Int + + external fun getControlReference(): ControlReference + + /** + * If the type is TYPE_INT, gets the current value. Otherwise, undefined behavior! + */ + external fun getIntValue(): Int + + /** + * If the type is TYPE_INT, sets the current value. Otherwise, undefined behavior! + */ + external fun setIntValue(value: Int) + + /** + * If the type is TYPE_INT, gets the default value. Otherwise, undefined behavior! + */ + external fun getIntDefaultValue(): Int + + /** + * If the type is TYPE_DOUBLE, gets the current value. Otherwise, undefined behavior! + */ + external fun getDoubleValue(): Double + + /** + * If the type is TYPE_DOUBLE, sets the current value. Otherwise, undefined behavior! + */ + external fun setDoubleValue(value: Double) + + /** + * If the type is TYPE_DOUBLE, gets the default value. Otherwise, undefined behavior! + */ + external fun getDoubleDefaultValue(): Double + + /** + * If the type is TYPE_DOUBLE, returns the minimum valid value. Otherwise, undefined behavior! + */ + external fun getDoubleMin(): Double + + /** + * If the type is TYPE_DOUBLE, returns the maximum valid value. Otherwise, undefined behavior! + */ + external fun getDoubleMax(): Double + + /** + * If the type is TYPE_BOOLEAN, gets the current value. Otherwise, undefined behavior! + */ + external fun getBooleanValue(): Boolean + + /** + * If the type is TYPE_BOOLEAN, sets the current value. Otherwise, undefined behavior! + */ + external fun setBooleanValue(value: Boolean) + + /** + * If the type is TYPE_BOOLEAN, gets the default value. Otherwise, undefined behavior! + */ + external fun getBooleanDefaultValue(): Boolean + + companion object { + const val TYPE_INT = 0 + const val TYPE_DOUBLE = 1 + const val TYPE_BOOLEAN = 2 + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 8cb63c420e..2ec27cff72 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -2295,23 +2295,23 @@ class SettingsFragmentPresenter( } private fun addNumericSetting(sl: ArrayList, setting: NumericSetting) { - when (setting.type) { + when (setting.getType()) { NumericSetting.TYPE_DOUBLE -> sl.add( FloatSliderSetting( InputMappingDoubleSetting(setting), - setting.uiName, + setting.getUiName(), "", - ceil(setting.doubleMin).toInt(), - floor(setting.doubleMax).toInt(), - setting.uiSuffix + ceil(setting.getDoubleMin()).toInt(), + floor(setting.getDoubleMax()).toInt(), + setting.getUiSuffix() ) ) NumericSetting.TYPE_BOOLEAN -> sl.add( SwitchSetting( InputMappingBooleanSetting(setting), - setting.uiName, - setting.uiDescription + setting.getUiName(), + setting.getUiDescription() ) ) } diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 67b6af4d71..2b437c10c0 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -732,7 +732,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) const jclass numeric_setting_class = env->FindClass("org/dolphinemu/dolphinemu/features/input/model/controlleremu/NumericSetting"); s_numeric_setting_class = reinterpret_cast(env->NewGlobalRef(numeric_setting_class)); - s_numeric_setting_pointer = env->GetFieldID(numeric_setting_class, "mPointer", "J"); + s_numeric_setting_pointer = env->GetFieldID(numeric_setting_class, "pointer", "J"); s_numeric_setting_constructor = env->GetMethodID(numeric_setting_class, "", "(J)V"); env->DeleteLocalRef(numeric_setting_class); From 24c882622fbcfc7dafee14824ba1f070b000b3aa Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:08:25 -0400 Subject: [PATCH 031/120] Android: Convert ControllerInterface to Kotlin --- .../input/model/ControllerInterface.java | 178 ------------------ .../input/model/ControllerInterface.kt | 150 +++++++++++++++ 2 files changed, 150 insertions(+), 178 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java deleted file mode 100644 index 732ed97a7c..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.java +++ /dev/null @@ -1,178 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model; - -import android.content.Context; -import android.hardware.input.InputManager; -import android.os.Build; -import android.os.Handler; -import android.os.VibrationEffect; -import android.os.Vibrator; -import android.os.VibratorManager; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; - -import androidx.annotation.Keep; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.dolphinemu.dolphinemu.DolphinApplication; -import org.dolphinemu.dolphinemu.utils.LooperThread; - -/** - * This class interfaces with the native ControllerInterface, - * which is where the emulator core gets inputs from. - */ -public final class ControllerInterface -{ - private static final class InputDeviceListener implements InputManager.InputDeviceListener - { - @Override - public void onInputDeviceAdded(int deviceId) - { - // Simple implementation for now. We could do something fancier if we wanted to. - refreshDevices(); - } - - @Override - public void onInputDeviceRemoved(int deviceId) - { - // Simple implementation for now. We could do something fancier if we wanted to. - refreshDevices(); - } - - @Override - public void onInputDeviceChanged(int deviceId) - { - // Simple implementation for now. We could do something fancier if we wanted to. - refreshDevices(); - } - } - - private static InputDeviceListener mInputDeviceListener; - private static LooperThread mLooperThread; - - /** - * Activities which want to pass on inputs to native code - * should call this in their own dispatchKeyEvent method. - * - * @return true if the emulator core seems to be interested in this event. - * false if the event should be passed on to the default dispatchKeyEvent. - */ - public static native boolean dispatchKeyEvent(KeyEvent event); - - /** - * Activities which want to pass on inputs to native code - * should call this in their own dispatchGenericMotionEvent method. - * - * @return true if the emulator core seems to be interested in this event. - * false if the event should be passed on to the default dispatchGenericMotionEvent. - */ - public static native boolean dispatchGenericMotionEvent(MotionEvent event); - - /** - * {@link DolphinSensorEventListener} calls this for each axis of a received SensorEvent. - * - * @return true if the emulator core seems to be interested in this event. - * false if the sensor can be suspended to save battery. - */ - public static native boolean dispatchSensorEvent(String deviceQualifier, String axisName, - float value); - - /** - * Called when a sensor is suspended or unsuspended. - * - * @param deviceQualifier A string used by native code for uniquely identifying devices. - * @param axisNames The name of all axes for the sensor. - * @param suspended Whether the sensor is now suspended. - */ - public static native void notifySensorSuspendedState(String deviceQualifier, String[] axisNames, - boolean suspended); - - /** - * Rescans for input devices. - */ - public static native void refreshDevices(); - - public static native String[] getAllDeviceStrings(); - - @Nullable - public static native CoreDevice getDevice(String deviceString); - - @Keep - private static void registerInputDeviceListener() - { - if (mLooperThread == null) - { - mLooperThread = new LooperThread("Hotplug thread"); - mLooperThread.start(); - } - - if (mInputDeviceListener == null) - { - InputManager im = (InputManager) - DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE); - - mInputDeviceListener = new InputDeviceListener(); - im.registerInputDeviceListener(mInputDeviceListener, new Handler(mLooperThread.getLooper())); - } - } - - @Keep - private static void unregisterInputDeviceListener() - { - if (mInputDeviceListener != null) - { - InputManager im = (InputManager) - DolphinApplication.getAppContext().getSystemService(Context.INPUT_SERVICE); - - im.unregisterInputDeviceListener(mInputDeviceListener); - mInputDeviceListener = null; - } - } - - @Keep @NonNull - private static DolphinVibratorManager getVibratorManager(InputDevice device) - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) - { - return new DolphinVibratorManagerPassthrough(device.getVibratorManager()); - } - else - { - return new DolphinVibratorManagerCompat(device.getVibrator()); - } - } - - @Keep @NonNull - private static DolphinVibratorManager getSystemVibratorManager() - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) - { - VibratorManager vibratorManager = (VibratorManager) - DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_MANAGER_SERVICE); - - if (vibratorManager != null) - return new DolphinVibratorManagerPassthrough(vibratorManager); - } - - Vibrator vibrator = (Vibrator) - DolphinApplication.getAppContext().getSystemService(Context.VIBRATOR_SERVICE); - - return new DolphinVibratorManagerCompat(vibrator); - } - - @Keep - private static void vibrate(@NonNull Vibrator vibrator) - { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - { - vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)); - } - else - { - vibrator.vibrate(100); - } - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.kt new file mode 100644 index 0000000000..3bca59f7b7 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/ControllerInterface.kt @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import android.content.Context +import android.hardware.input.InputManager +import android.os.Build +import android.os.Handler +import android.os.VibrationEffect +import android.os.Vibrator +import android.os.VibratorManager +import android.view.InputDevice +import android.view.KeyEvent +import android.view.MotionEvent +import androidx.annotation.Keep +import org.dolphinemu.dolphinemu.DolphinApplication +import org.dolphinemu.dolphinemu.utils.LooperThread + +/** + * This class interfaces with the native ControllerInterface, + * which is where the emulator core gets inputs from. + */ +object ControllerInterface { + private var inputDeviceListener: InputDeviceListener? = null + private lateinit var looperThread: LooperThread + + /** + * Activities which want to pass on inputs to native code + * should call this in their own dispatchKeyEvent method. + * + * @return true if the emulator core seems to be interested in this event. + * false if the event should be passed on to the default dispatchKeyEvent. + */ + external fun dispatchKeyEvent(event: KeyEvent): Boolean + + /** + * Activities which want to pass on inputs to native code + * should call this in their own dispatchGenericMotionEvent method. + * + * @return true if the emulator core seems to be interested in this event. + * false if the event should be passed on to the default dispatchGenericMotionEvent. + */ + external fun dispatchGenericMotionEvent(event: MotionEvent): Boolean + + /** + * [DolphinSensorEventListener] calls this for each axis of a received SensorEvent. + * + * @return true if the emulator core seems to be interested in this event. + * false if the sensor can be suspended to save battery. + */ + external fun dispatchSensorEvent( + deviceQualifier: String, + axisName: String, + value: Float + ): Boolean + + /** + * Called when a sensor is suspended or unsuspended. + * + * @param deviceQualifier A string used by native code for uniquely identifying devices. + * @param axisNames The name of all axes for the sensor. + * @param suspended Whether the sensor is now suspended. + */ + external fun notifySensorSuspendedState( + deviceQualifier: String, + axisNames: Array, + suspended: Boolean + ) + + /** + * Rescans for input devices. + */ + external fun refreshDevices() + + external fun getAllDeviceStrings(): Array + + external fun getDevice(deviceString: String): CoreDevice? + + @Keep + @JvmStatic + private fun registerInputDeviceListener() { + looperThread = LooperThread("Hotplug thread") + looperThread.start() + + if (inputDeviceListener == null) { + val im = DolphinApplication.getAppContext() + .getSystemService(Context.INPUT_SERVICE) as InputManager? + + inputDeviceListener = InputDeviceListener() + im!!.registerInputDeviceListener(inputDeviceListener, Handler(looperThread.looper)) + } + } + + @Keep + @JvmStatic + private fun unregisterInputDeviceListener() { + if (inputDeviceListener != null) { + val im = DolphinApplication.getAppContext() + .getSystemService(Context.INPUT_SERVICE) as InputManager? + + im!!.unregisterInputDeviceListener(inputDeviceListener) + inputDeviceListener = null + } + } + + @Keep + @JvmStatic + private fun getVibratorManager(device: InputDevice): DolphinVibratorManager { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + DolphinVibratorManagerPassthrough(device.vibratorManager) + } else { + DolphinVibratorManagerCompat(device.vibrator) + } + } + + @Keep + @JvmStatic + private fun getSystemVibratorManager(): DolphinVibratorManager { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val vibratorManager = DolphinApplication.getAppContext() + .getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager? + if (vibratorManager != null) + return DolphinVibratorManagerPassthrough(vibratorManager) + } + val vibrator = DolphinApplication.getAppContext() + .getSystemService(Context.VIBRATOR_SERVICE) as Vibrator + return DolphinVibratorManagerCompat(vibrator) + } + + @Keep + @JvmStatic + private fun vibrate(vibrator: Vibrator) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + vibrator.vibrate(VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE)) + } else { + vibrator.vibrate(100) + } + } + + private class InputDeviceListener : InputManager.InputDeviceListener { + // Simple implementation for now. We could do something fancier if we wanted to. + override fun onInputDeviceAdded(deviceId: Int) = refreshDevices() + + // Simple implementation for now. We could do something fancier if we wanted to. + override fun onInputDeviceRemoved(deviceId: Int) = refreshDevices() + + // Simple implementation for now. We could do something fancier if we wanted to. + override fun onInputDeviceChanged(deviceId: Int) = refreshDevices() + } +} From ba9f2373c0d017ff094dd99a068c6b8a6afd80dc Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:08:42 -0400 Subject: [PATCH 032/120] Android: Convert DolphinSensorEventListener to Kotlin --- .../model/DolphinSensorEventListener.java | 440 --------------- .../input/model/DolphinSensorEventListener.kt | 504 ++++++++++++++++++ 2 files changed, 504 insertions(+), 440 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java deleted file mode 100644 index 78d0e4785d..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.java +++ /dev/null @@ -1,440 +0,0 @@ -package org.dolphinemu.dolphinemu.features.input.model; - -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.os.Build; -import android.view.InputDevice; -import android.view.Surface; - -import androidx.annotation.Keep; - -import org.dolphinemu.dolphinemu.DolphinApplication; -import org.dolphinemu.dolphinemu.utils.Log; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class DolphinSensorEventListener implements SensorEventListener -{ - // Set of three axes. Creates a negative companion to each axis, and corrects for device rotation. - private static final int AXIS_SET_TYPE_DEVICE_COORDINATES = 0; - // Set of three axes. Creates a negative companion to each axis. - private static final int AXIS_SET_TYPE_OTHER_COORDINATES = 1; - - private static class AxisSetDetails - { - public final int firstAxisOfSet; - public final int axisSetType; - - public AxisSetDetails(int firstAxisOfSet, int axisSetType) - { - this.firstAxisOfSet = firstAxisOfSet; - this.axisSetType = axisSetType; - } - } - - private static class SensorDetails - { - public final int sensorType; - public final String[] axisNames; - public final AxisSetDetails[] axisSetDetails; - public boolean isSuspended = true; - - public SensorDetails(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails) - { - this.sensorType = sensorType; - this.axisNames = axisNames; - this.axisSetDetails = axisSetDetails; - } - } - - private static int sDeviceRotation = Surface.ROTATION_0; - - private final SensorManager mSensorManager; - - private final HashMap mSensorDetails = new HashMap<>(); - - private final boolean mRotateCoordinatesForScreenOrientation; - - private String mDeviceQualifier = ""; - - // The fastest sampling rate Android lets us use without declaring the HIGH_SAMPLING_RATE_SENSORS - // permission is 200 Hz. This is also the sampling rate of a Wii Remote, so it fits us perfectly. - private static final int SAMPLING_PERIOD_US = 1000000 / 200; - - @Keep - public DolphinSensorEventListener() - { - mSensorManager = (SensorManager) - DolphinApplication.getAppContext().getSystemService(Context.SENSOR_SERVICE); - mRotateCoordinatesForScreenOrientation = true; - - addSensors(); - } - - @Keep - public DolphinSensorEventListener(InputDevice inputDevice) - { - mRotateCoordinatesForScreenOrientation = false; - - if (Build.VERSION.SDK_INT >= 31) - { - mSensorManager = inputDevice.getSensorManager(); - - // TODO: There is a bug where after suspending sensors, onSensorChanged can get called for - // a sensor that we never registered as a listener for. The way our code is currently written, - // this causes a NullPointerException, but if we checked for null we would instead have the - // problem of being spammed with onSensorChanged calls even though the sensor shouldn't be - // enabled. For now, let's comment out the ability to use InputDevice sensors. - - //addSensors(); - } - else - { - mSensorManager = null; - } - } - - private void addSensors() - { - tryAddSensor(Sensor.TYPE_ACCELEROMETER, new String[]{"Accel Right", "Accel Left", - "Accel Forward", "Accel Backward", "Accel Up", "Accel Down"}, - new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); - - tryAddSensor(Sensor.TYPE_GYROSCOPE, new String[]{"Gyro Pitch Up", "Gyro Pitch Down", - "Gyro Roll Right", "Gyro Roll Left", "Gyro Yaw Left", "Gyro Yaw Right"}, - new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); - - tryAddSensor(Sensor.TYPE_LIGHT, "Light"); - - tryAddSensor(Sensor.TYPE_PRESSURE, "Pressure"); - - tryAddSensor(Sensor.TYPE_TEMPERATURE, "Device Temperature"); - - tryAddSensor(Sensor.TYPE_PROXIMITY, "Proximity"); - - tryAddSensor(Sensor.TYPE_GRAVITY, new String[]{"Gravity Right", "Gravity Left", - "Gravity Forward", "Gravity Backward", "Gravity Up", "Gravity Down"}, - new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); - - tryAddSensor(Sensor.TYPE_LINEAR_ACCELERATION, - new String[]{"Linear Acceleration Right", "Linear Acceleration Left", - "Linear Acceleration Forward", "Linear Acceleration Backward", - "Linear Acceleration Up", "Linear Acceleration Down"}, - new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); - - // The values provided by this sensor can be interpreted as an Euler vector or a quaternion. - // The directions of X and Y are flipped to match the Wii Remote coordinate system. - tryAddSensor(Sensor.TYPE_ROTATION_VECTOR, - new String[]{"Rotation Vector X-", "Rotation Vector X+", "Rotation Vector Y-", - "Rotation Vector Y+", "Rotation Vector Z+", - "Rotation Vector Z-", "Rotation Vector R", "Rotation Vector Heading Accuracy"}, - new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); - - tryAddSensor(Sensor.TYPE_RELATIVE_HUMIDITY, "Relative Humidity"); - - tryAddSensor(Sensor.TYPE_AMBIENT_TEMPERATURE, "Ambient Temperature"); - - // The values provided by this sensor can be interpreted as an Euler vector or a quaternion. - // The directions of X and Y are flipped to match the Wii Remote coordinate system. - tryAddSensor(Sensor.TYPE_GAME_ROTATION_VECTOR, - new String[]{"Game Rotation Vector X-", "Game Rotation Vector X+", - "Game Rotation Vector Y-", "Game Rotation Vector Y+", "Game Rotation Vector Z+", - "Game Rotation Vector Z-", "Game Rotation Vector R"}, - new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)}); - - tryAddSensor(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, - new String[]{"Gyro Uncalibrated Pitch Up", "Gyro Uncalibrated Pitch Down", - "Gyro Uncalibrated Roll Right", "Gyro Uncalibrated Roll Left", - "Gyro Uncalibrated Yaw Left", "Gyro Uncalibrated Yaw Right", - "Gyro Drift Pitch Up", "Gyro Drift Pitch Down", "Gyro Drift Roll Right", - "Gyro Drift Roll Left", "Gyro Drift Yaw Left", "Gyro Drift Yaw Right"}, - new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES), - new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)}); - - tryAddSensor(Sensor.TYPE_HEART_RATE, "Heart Rate"); - - if (Build.VERSION.SDK_INT >= 24) - { - tryAddSensor(Sensor.TYPE_HEART_BEAT, "Heart Beat"); - } - - if (Build.VERSION.SDK_INT >= 26) - { - tryAddSensor(Sensor.TYPE_ACCELEROMETER_UNCALIBRATED, - new String[]{"Accel Uncalibrated Right", "Accel Uncalibrated Left", - "Accel Uncalibrated Forward", "Accel Uncalibrated Backward", - "Accel Uncalibrated Up", "Accel Uncalibrated Down", - "Accel Bias Right", "Accel Bias Left", "Accel Bias Forward", - "Accel Bias Backward", "Accel Bias Up", "Accel Bias Down"}, - new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES), - new AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES)}); - } - - if (Build.VERSION.SDK_INT >= 30) - { - tryAddSensor(Sensor.TYPE_HINGE_ANGLE, "Hinge Angle"); - } - - if (Build.VERSION.SDK_INT >= 33) - { - // The values provided by this sensor can be interpreted as an Euler vector. - // The directions of X and Y are flipped to match the Wii Remote coordinate system. - tryAddSensor(Sensor.TYPE_HEAD_TRACKER, - new String[]{"Head Rotation Vector X-", "Head Rotation Vector X+", - "Head Rotation Vector Y-", "Head Rotation Vector Y+", - "Head Rotation Vector Z+", "Head Rotation Vector Z-", - "Head Pitch Up", "Head Pitch Down", "Head Roll Right", "Head Roll Left", - "Head Yaw Left", "Head Yaw Right"}, - new AxisSetDetails[]{new AxisSetDetails(0, AXIS_SET_TYPE_OTHER_COORDINATES), - new AxisSetDetails(3, AXIS_SET_TYPE_OTHER_COORDINATES)}); - - tryAddSensor(Sensor.TYPE_HEADING, new String[]{"Heading", "Heading Accuracy"}, - new AxisSetDetails[]{}); - } - } - - private void tryAddSensor(int sensorType, String axisName) - { - tryAddSensor(sensorType, new String[]{axisName}, new AxisSetDetails[]{}); - } - - private void tryAddSensor(int sensorType, String[] axisNames, AxisSetDetails[] axisSetDetails) - { - Sensor sensor = mSensorManager.getDefaultSensor(sensorType); - if (sensor != null) - { - mSensorDetails.put(sensor, new SensorDetails(sensorType, axisNames, axisSetDetails)); - } - } - - @Override - public void onSensorChanged(SensorEvent sensorEvent) - { - final SensorDetails sensorDetails = mSensorDetails.get(sensorEvent.sensor); - - final float[] values = sensorEvent.values; - final String[] axisNames = sensorDetails.axisNames; - final AxisSetDetails[] axisSetDetails = sensorDetails.axisSetDetails; - - int eventAxisIndex = 0; - int detailsAxisIndex = 0; - int detailsAxisSetIndex = 0; - boolean keepSensorAlive = false; - while (eventAxisIndex < values.length && detailsAxisIndex < axisNames.length) - { - if (detailsAxisSetIndex < axisSetDetails.length && - axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex) - { - int rotation = Surface.ROTATION_0; - if (mRotateCoordinatesForScreenOrientation && - axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES) - { - rotation = sDeviceRotation; - } - - float x, y; - switch (rotation) - { - default: - case Surface.ROTATION_0: - x = values[eventAxisIndex]; - y = values[eventAxisIndex + 1]; - break; - case Surface.ROTATION_90: - x = -values[eventAxisIndex + 1]; - y = values[eventAxisIndex]; - break; - case Surface.ROTATION_180: - x = -values[eventAxisIndex]; - y = -values[eventAxisIndex + 1]; - break; - case Surface.ROTATION_270: - x = values[eventAxisIndex + 1]; - y = -values[eventAxisIndex]; - break; - } - - float z = values[eventAxisIndex + 2]; - - keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, - axisNames[detailsAxisIndex], x); - keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, - axisNames[detailsAxisIndex + 1], x); - keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, - axisNames[detailsAxisIndex + 2], y); - keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, - axisNames[detailsAxisIndex + 3], y); - keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, - axisNames[detailsAxisIndex + 4], z); - keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, - axisNames[detailsAxisIndex + 5], z); - - eventAxisIndex += 3; - detailsAxisIndex += 6; - detailsAxisSetIndex++; - } - else - { - keepSensorAlive |= ControllerInterface.dispatchSensorEvent(mDeviceQualifier, - axisNames[detailsAxisIndex], values[eventAxisIndex]); - - eventAxisIndex++; - detailsAxisIndex++; - } - } - - if (!keepSensorAlive) - { - setSensorSuspended(sensorEvent.sensor, sensorDetails, true); - } - } - - @Override - public void onAccuracyChanged(Sensor sensor, int i) - { - // We don't care about this - } - - /** - * The device qualifier set here will be passed on to native code, - * for the purpose of letting native code identify which device this object belongs to. - */ - @Keep - public void setDeviceQualifier(String deviceQualifier) - { - mDeviceQualifier = deviceQualifier; - } - - /** - * If a sensor has been suspended to save battery, this unsuspends it. - * If the sensor isn't currently suspended, nothing happens. - * - * @param axisName The name of any of the sensor's axes. - */ - @Keep - public void requestUnsuspendSensor(String axisName) - { - for (Map.Entry entry : mSensorDetails.entrySet()) - { - if (Arrays.asList(entry.getValue().axisNames).contains(axisName)) - { - setSensorSuspended(entry.getKey(), entry.getValue(), false); - } - } - } - - private void setSensorSuspended(Sensor sensor, SensorDetails sensorDetails, boolean suspend) - { - boolean changeOccurred = false; - - synchronized (sensorDetails) - { - if (sensorDetails.isSuspended != suspend) - { - ControllerInterface.notifySensorSuspendedState(mDeviceQualifier, sensorDetails.axisNames, - suspend); - - if (suspend) - mSensorManager.unregisterListener(this, sensor); - else - mSensorManager.registerListener(this, sensor, SAMPLING_PERIOD_US); - - sensorDetails.isSuspended = suspend; - - changeOccurred = true; - } - } - - if (changeOccurred) - { - Log.info((suspend ? "Suspended sensor " : "Unsuspended sensor ") + sensor.getName()); - } - } - - @Keep - public String[] getAxisNames() - { - ArrayList axisNames = new ArrayList<>(); - - for (SensorDetails sensorDetails : getSensorDetailsSorted()) - { - Collections.addAll(axisNames, sensorDetails.axisNames); - } - - return axisNames.toArray(new String[]{}); - } - - @Keep - public boolean[] getNegativeAxes() - { - ArrayList negativeAxes = new ArrayList<>(); - - for (SensorDetails sensorDetails : getSensorDetailsSorted()) - { - int eventAxisIndex = 0; - int detailsAxisIndex = 0; - int detailsAxisSetIndex = 0; - while (detailsAxisIndex < sensorDetails.axisNames.length) - { - if (detailsAxisSetIndex < sensorDetails.axisSetDetails.length && - sensorDetails.axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex) - { - negativeAxes.add(false); - negativeAxes.add(true); - negativeAxes.add(false); - negativeAxes.add(true); - negativeAxes.add(false); - negativeAxes.add(true); - - eventAxisIndex += 3; - detailsAxisIndex += 6; - detailsAxisSetIndex++; - } - else - { - negativeAxes.add(false); - - eventAxisIndex++; - detailsAxisIndex++; - } - } - } - - boolean[] result = new boolean[negativeAxes.size()]; - for (int i = 0; i < result.length; i++) - { - result[i] = negativeAxes.get(i); - } - - return result; - } - - private List getSensorDetailsSorted() - { - ArrayList sensorDetails = new ArrayList<>(mSensorDetails.values()); - Collections.sort(sensorDetails, Comparator.comparingInt(s -> s.sensorType)); - return sensorDetails; - } - - /** - * Should be called when an activity or other component that uses sensor events is resumed. - * - * Sensor events that contain device coordinates will have the coordinates rotated by the value - * passed to this function. - * - * @param deviceRotation The current rotation of the device (i.e. rotation of the default display) - */ - public static void setDeviceRotation(int deviceRotation) - { - sDeviceRotation = deviceRotation; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.kt new file mode 100644 index 0000000000..e89143aa31 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinSensorEventListener.kt @@ -0,0 +1,504 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import android.content.Context +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.os.Build +import android.view.InputDevice +import android.view.Surface +import androidx.annotation.Keep +import org.dolphinemu.dolphinemu.DolphinApplication +import org.dolphinemu.dolphinemu.utils.Log +import java.util.Collections + +class DolphinSensorEventListener : SensorEventListener { + private class AxisSetDetails(val firstAxisOfSet: Int, val axisSetType: Int) + + private class SensorDetails( + val sensorType: Int, + val axisNames: Array, + val axisSetDetails: Array + ) { + var isSuspended = true + } + + private val sensorManager: SensorManager? + + private val sensorDetails = HashMap() + + private val rotateCoordinatesForScreenOrientation: Boolean + + private var deviceQualifier = "" + + @Keep + constructor() { + sensorManager = DolphinApplication.getAppContext() + .getSystemService(Context.SENSOR_SERVICE) as SensorManager? + rotateCoordinatesForScreenOrientation = true + addSensors() + } + + @Keep + constructor(inputDevice: InputDevice) { + rotateCoordinatesForScreenOrientation = false + sensorManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + inputDevice.sensorManager + + // TODO: There is a bug where after suspending sensors, onSensorChanged can get called for + // a sensor that we never registered as a listener for. The way our code is currently written, + // this causes a NullPointerException, but if we checked for null we would instead have the + // problem of being spammed with onSensorChanged calls even though the sensor shouldn't be + // enabled. For now, let's comment out the ability to use InputDevice sensors. + + //addSensors(); + } else { + null + } + } + + private fun addSensors() { + tryAddSensor( + Sensor.TYPE_ACCELEROMETER, + arrayOf( + "Accel Right", + "Accel Left", + "Accel Forward", + "Accel Backward", + "Accel Up", + "Accel Down" + ), + arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)) + ) + + tryAddSensor( + Sensor.TYPE_GYROSCOPE, + arrayOf( + "Gyro Pitch Up", + "Gyro Pitch Down", + "Gyro Roll Right", + "Gyro Roll Left", + "Gyro Yaw Left", + "Gyro Yaw Right" + ), + arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)) + ) + + tryAddSensor(Sensor.TYPE_LIGHT, "Light") + + tryAddSensor(Sensor.TYPE_PRESSURE, "Pressure") + + tryAddSensor(Sensor.TYPE_TEMPERATURE, "Device Temperature") + + tryAddSensor(Sensor.TYPE_PROXIMITY, "Proximity") + + tryAddSensor( + Sensor.TYPE_GRAVITY, + arrayOf( + "Gravity Right", + "Gravity Left", + "Gravity Forward", + "Gravity Backward", + "Gravity Up", + "Gravity Down" + ), + arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)) + ) + + tryAddSensor( + Sensor.TYPE_LINEAR_ACCELERATION, + arrayOf( + "Linear Acceleration Right", + "Linear Acceleration Left", + "Linear Acceleration Forward", + "Linear Acceleration Backward", + "Linear Acceleration Up", + "Linear Acceleration Down" + ), + arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)) + ) + + // The values provided by this sensor can be interpreted as an Euler vector or a quaternion. + // The directions of X and Y are flipped to match the Wii Remote coordinate system. + tryAddSensor( + Sensor.TYPE_ROTATION_VECTOR, + arrayOf( + "Rotation Vector X-", + "Rotation Vector X+", + "Rotation Vector Y-", + "Rotation Vector Y+", + "Rotation Vector Z+", + "Rotation Vector Z-", + "Rotation Vector R", + "Rotation Vector Heading Accuracy" + ), + arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)) + ) + + tryAddSensor(Sensor.TYPE_RELATIVE_HUMIDITY, "Relative Humidity") + + tryAddSensor(Sensor.TYPE_AMBIENT_TEMPERATURE, "Ambient Temperature") + + // The values provided by this sensor can be interpreted as an Euler vector or a quaternion. + // The directions of X and Y are flipped to match the Wii Remote coordinate system. + tryAddSensor( + Sensor.TYPE_GAME_ROTATION_VECTOR, + arrayOf( + "Game Rotation Vector X-", + "Game Rotation Vector X+", + "Game Rotation Vector Y-", + "Game Rotation Vector Y+", + "Game Rotation Vector Z+", + "Game Rotation Vector Z-", + "Game Rotation Vector R" + ), + arrayOf(AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES)) + ) + + tryAddSensor( + Sensor.TYPE_GYROSCOPE_UNCALIBRATED, + arrayOf( + "Gyro Uncalibrated Pitch Up", + "Gyro Uncalibrated Pitch Down", + "Gyro Uncalibrated Roll Right", + "Gyro Uncalibrated Roll Left", + "Gyro Uncalibrated Yaw Left", + "Gyro Uncalibrated Yaw Right", + "Gyro Drift Pitch Up", + "Gyro Drift Pitch Down", + "Gyro Drift Roll Right", + "Gyro Drift Roll Left", + "Gyro Drift Yaw Left", + "Gyro Drift Yaw Right" + ), + arrayOf( + AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES), + AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES) + ) + ) + + tryAddSensor(Sensor.TYPE_HEART_RATE, "Heart Rate") + + if (Build.VERSION.SDK_INT >= 24) { + tryAddSensor(Sensor.TYPE_HEART_BEAT, "Heart Beat") + } + + if (Build.VERSION.SDK_INT >= 26) { + tryAddSensor( + Sensor.TYPE_ACCELEROMETER_UNCALIBRATED, + arrayOf( + "Accel Uncalibrated Right", + "Accel Uncalibrated Left", + "Accel Uncalibrated Forward", + "Accel Uncalibrated Backward", + "Accel Uncalibrated Up", + "Accel Uncalibrated Down", + "Accel Bias Right", + "Accel Bias Left", + "Accel Bias Forward", + "Accel Bias Backward", + "Accel Bias Up", + "Accel Bias Down" + ), + arrayOf( + AxisSetDetails(0, AXIS_SET_TYPE_DEVICE_COORDINATES), + AxisSetDetails(3, AXIS_SET_TYPE_DEVICE_COORDINATES) + ) + ) + } + + if (Build.VERSION.SDK_INT >= 30) { + tryAddSensor(Sensor.TYPE_HINGE_ANGLE, "Hinge Angle") + } + + if (Build.VERSION.SDK_INT >= 33) { + // The values provided by this sensor can be interpreted as an Euler vector. + // The directions of X and Y are flipped to match the Wii Remote coordinate system. + tryAddSensor( + Sensor.TYPE_HEAD_TRACKER, + arrayOf( + "Head Rotation Vector X-", + "Head Rotation Vector X+", + "Head Rotation Vector Y-", + "Head Rotation Vector Y+", + "Head Rotation Vector Z+", + "Head Rotation Vector Z-", + "Head Pitch Up", + "Head Pitch Down", + "Head Roll Right", + "Head Roll Left", + "Head Yaw Left", + "Head Yaw Right" + ), + arrayOf( + AxisSetDetails(0, AXIS_SET_TYPE_OTHER_COORDINATES), + AxisSetDetails(3, AXIS_SET_TYPE_OTHER_COORDINATES) + ) + ) + + tryAddSensor(Sensor.TYPE_HEADING, arrayOf("Heading", "Heading Accuracy"), arrayOf()) + } + } + + private fun tryAddSensor(sensorType: Int, axisName: String) { + tryAddSensor(sensorType, arrayOf(axisName), arrayOf()) + } + + private fun tryAddSensor( + sensorType: Int, + axisNames: Array, + axisSetDetails: Array + ) { + val sensor = sensorManager!!.getDefaultSensor(sensorType) + if (sensor != null) { + sensorDetails[sensor] = SensorDetails(sensorType, axisNames, axisSetDetails) + } + } + + override fun onSensorChanged(sensorEvent: SensorEvent) { + val sensorDetails = sensorDetails[sensorEvent.sensor] + + val values = sensorEvent.values + val axisNames = sensorDetails!!.axisNames + val axisSetDetails = sensorDetails.axisSetDetails + + var eventAxisIndex = 0 + var detailsAxisIndex = 0 + var detailsAxisSetIndex = 0 + var keepSensorAlive = false + while (eventAxisIndex < values.size && detailsAxisIndex < axisNames.size) { + if (detailsAxisSetIndex < axisSetDetails.size && + axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex + ) { + var rotation = Surface.ROTATION_0 + if (rotateCoordinatesForScreenOrientation && + axisSetDetails[detailsAxisSetIndex].axisSetType == AXIS_SET_TYPE_DEVICE_COORDINATES + ) { + rotation = deviceRotation + } + + var x: Float + var y: Float + when (rotation) { + Surface.ROTATION_0 -> { + x = values[eventAxisIndex] + y = values[eventAxisIndex + 1] + } + + Surface.ROTATION_90 -> { + x = -values[eventAxisIndex + 1] + y = values[eventAxisIndex] + } + + Surface.ROTATION_180 -> { + x = -values[eventAxisIndex] + y = -values[eventAxisIndex + 1] + } + + Surface.ROTATION_270 -> { + x = values[eventAxisIndex + 1] + y = -values[eventAxisIndex] + } + + else -> { + x = values[eventAxisIndex] + y = values[eventAxisIndex + 1] + } + } + + val z = values[eventAxisIndex + 2] + + keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent( + deviceQualifier, + axisNames[detailsAxisIndex], + x + ) + keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent( + deviceQualifier, + axisNames[detailsAxisIndex + 1], + x + ) + keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent( + deviceQualifier, + axisNames[detailsAxisIndex + 2], + y + ) + keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent( + deviceQualifier, + axisNames[detailsAxisIndex + 3], + y + ) + keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent( + deviceQualifier, + axisNames[detailsAxisIndex + 4], + z + ) + keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent( + deviceQualifier, + axisNames[detailsAxisIndex + 5], + z + ) + + eventAxisIndex += 3 + detailsAxisIndex += 6 + detailsAxisSetIndex++ + } else { + keepSensorAlive = keepSensorAlive or ControllerInterface.dispatchSensorEvent( + deviceQualifier, + axisNames[detailsAxisIndex], values[eventAxisIndex] + ) + + eventAxisIndex++ + detailsAxisIndex++ + } + } + if (!keepSensorAlive) { + setSensorSuspended(sensorEvent.sensor, sensorDetails, true) + } + } + + override fun onAccuracyChanged(sensor: Sensor, i: Int) { + // We don't care about this + } + + /** + * The device qualifier set here will be passed on to native code, + * for the purpose of letting native code identify which device this object belongs to. + */ + @Keep + fun setDeviceQualifier(deviceQualifier: String) { + this.deviceQualifier = deviceQualifier + } + + /** + * If a sensor has been suspended to save battery, this unsuspends it. + * If the sensor isn't currently suspended, nothing happens. + * + * @param axisName The name of any of the sensor's axes. + */ + @Keep + fun requestUnsuspendSensor(axisName: String) { + for ((key, value) in sensorDetails) { + if (listOf(*value.axisNames).contains(axisName)) { + setSensorSuspended(key, value, false) + } + } + } + + private fun setSensorSuspended( + sensor: Sensor, + sensorDetails: SensorDetails, + suspend: Boolean + ) { + var changeOccurred = false + + synchronized(sensorDetails) { + if (sensorDetails.isSuspended != suspend) { + ControllerInterface.notifySensorSuspendedState( + deviceQualifier, + sensorDetails.axisNames, + suspend + ) + + if (suspend) + sensorManager!!.unregisterListener(this, sensor) + else + sensorManager!!.registerListener(this, sensor, SAMPLING_PERIOD_US) + + sensorDetails.isSuspended = suspend + + changeOccurred = true + } + } + + if (changeOccurred) { + Log.info((if (suspend) "Suspended sensor " else "Unsuspended sensor ") + sensor.name) + } + } + + @Keep + fun getAxisNames(): Array { + val axisNames = ArrayList() + for (sensorDetails in sensorDetailsSorted) { + sensorDetails.axisNames.forEach { axisNames.add(it) } + } + return axisNames.toArray(arrayOf()) + } + + @Keep + fun getNegativeAxes(): BooleanArray { + val negativeAxes = ArrayList() + + for (sensorDetails in sensorDetailsSorted) { + var eventAxisIndex = 0 + var detailsAxisIndex = 0 + var detailsAxisSetIndex = 0 + while (detailsAxisIndex < sensorDetails.axisNames.size) { + if (detailsAxisSetIndex < sensorDetails.axisSetDetails.size && + sensorDetails.axisSetDetails[detailsAxisSetIndex].firstAxisOfSet == eventAxisIndex + ) { + negativeAxes.add(false) + negativeAxes.add(true) + negativeAxes.add(false) + negativeAxes.add(true) + negativeAxes.add(false) + negativeAxes.add(true) + + eventAxisIndex += 3 + detailsAxisIndex += 6 + detailsAxisSetIndex++ + } else { + negativeAxes.add(false) + + eventAxisIndex++ + detailsAxisIndex++ + } + } + } + + val result = BooleanArray(negativeAxes.size) + for (i in result.indices) { + result[i] = negativeAxes[i] + } + + return result + } + + private val sensorDetailsSorted: List + get() { + val sensorDetails = ArrayList(sensorDetails.values) + Collections.sort( + sensorDetails, + Comparator.comparingInt { s: SensorDetails -> s.sensorType } + ) + return sensorDetails + } + + companion object { + // Set of three axes. Creates a negative companion to each axis, and corrects for device rotation. + private const val AXIS_SET_TYPE_DEVICE_COORDINATES = 0 + + // Set of three axes. Creates a negative companion to each axis. + private const val AXIS_SET_TYPE_OTHER_COORDINATES = 1 + private var deviceRotation = Surface.ROTATION_0 + + // The fastest sampling rate Android lets us use without declaring the HIGH_SAMPLING_RATE_SENSORS + // permission is 200 Hz. This is also the sampling rate of a Wii Remote, so it fits us perfectly. + private const val SAMPLING_PERIOD_US = 1000000 / 200 + + /** + * Should be called when an activity or other component that uses sensor events is resumed. + * + * Sensor events that contain device coordinates will have the coordinates rotated by the value + * passed to this function. + * + * @param deviceRotation The current rotation of the device (i.e. rotation of the default display) + */ + fun setDeviceRotation(deviceRotation: Int) { + this.deviceRotation = deviceRotation + } + } +} From 1ff6a3788e7f45dec90bdfa8386037ba094de174 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:14:00 -0400 Subject: [PATCH 033/120] Android: Convert DolphinVibratorManager to Kotlin --- .../input/model/DolphinVibratorManager.java | 20 ------------------- .../input/model/DolphinVibratorManager.kt | 16 +++++++++++++++ 2 files changed, 16 insertions(+), 20 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java deleted file mode 100644 index abc04d969d..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.java +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model; - -import android.os.Vibrator; - -import androidx.annotation.Keep; -import androidx.annotation.NonNull; - -/** - * A wrapper around {@link android.os.VibratorManager}, for backwards compatibility. - */ -public interface DolphinVibratorManager -{ - @Keep @NonNull - Vibrator getVibrator(int vibratorId); - - @Keep @NonNull - int[] getVibratorIds(); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.kt new file mode 100644 index 0000000000..c3c6ba28da --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManager.kt @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import android.os.Vibrator +import androidx.annotation.Keep + +/** + * A wrapper around [android.os.VibratorManager], for backwards compatibility. + */ +@Keep +interface DolphinVibratorManager { + fun getVibrator(vibratorId: Int): Vibrator + + fun getVibratorIds(): IntArray +} From 29adbb439457c83d63fdfd217f4f7edeba20579f Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:14:20 -0400 Subject: [PATCH 034/120] Android: Convert DolphinVibratorManagerCompat to Kotlin --- .../model/DolphinVibratorManagerCompat.java | 35 ------------------- .../model/DolphinVibratorManagerCompat.kt | 24 +++++++++++++ 2 files changed, 24 insertions(+), 35 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java deleted file mode 100644 index 40c4fa95d1..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.java +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model; - -import android.os.Vibrator; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public final class DolphinVibratorManagerCompat implements DolphinVibratorManager -{ - private final Vibrator mVibrator; - private final int[] mIds; - - public DolphinVibratorManagerCompat(@Nullable Vibrator vibrator) - { - mVibrator = vibrator; - mIds = vibrator != null && vibrator.hasVibrator() ? new int[]{0} : new int[]{}; - } - - @Override @NonNull - public Vibrator getVibrator(int vibratorId) - { - if (vibratorId > mIds.length) - throw new IndexOutOfBoundsException(); - - return mVibrator; - } - - @Override @NonNull - public int[] getVibratorIds() - { - return mIds; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.kt new file mode 100644 index 0000000000..039f0ecb6b --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerCompat.kt @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import android.os.Vibrator + +class DolphinVibratorManagerCompat(vibrator: Vibrator) : DolphinVibratorManager { + private val vibrator: Vibrator + private val vibratorIds: IntArray + + init { + this.vibrator = vibrator + vibratorIds = if (vibrator.hasVibrator()) intArrayOf(0) else intArrayOf() + } + + override fun getVibrator(vibratorId: Int): Vibrator { + if (vibratorId > vibratorIds.size) + throw IndexOutOfBoundsException() + + return vibrator + } + + override fun getVibratorIds(): IntArray = vibratorIds +} From 4c8cd49d8037232240638f255a9bcaf927be601d Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:14:38 -0400 Subject: [PATCH 035/120] Android: Convert DolphinVibratorManagerPassthrough to Kotlin --- .../DolphinVibratorManagerPassthrough.java | 33 ------------------- .../DolphinVibratorManagerPassthrough.kt | 16 +++++++++ 2 files changed, 16 insertions(+), 33 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java deleted file mode 100644 index 2ca747f54f..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.java +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model; - -import android.os.Build; -import android.os.Vibrator; -import android.os.VibratorManager; - -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; - -@RequiresApi(api = Build.VERSION_CODES.S) -public final class DolphinVibratorManagerPassthrough implements DolphinVibratorManager -{ - private final VibratorManager mVibratorManager; - - public DolphinVibratorManagerPassthrough(@NonNull VibratorManager vibratorManager) - { - mVibratorManager = vibratorManager; - } - - @Override @NonNull - public Vibrator getVibrator(int vibratorId) - { - return mVibratorManager.getVibrator(vibratorId); - } - - @Override @NonNull - public int[] getVibratorIds() - { - return mVibratorManager.getVibratorIds(); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.kt new file mode 100644 index 0000000000..0895484314 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/DolphinVibratorManagerPassthrough.kt @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import android.os.Build +import android.os.Vibrator +import android.os.VibratorManager +import androidx.annotation.RequiresApi + +@RequiresApi(api = Build.VERSION_CODES.S) +class DolphinVibratorManagerPassthrough(private val vibratorManager: VibratorManager) : + DolphinVibratorManager { + override fun getVibrator(vibratorId: Int): Vibrator = vibratorManager.getVibrator(vibratorId) + + override fun getVibratorIds(): IntArray = vibratorManager.vibratorIds +} From b2e2c3b8d4c24dd79fab4f59ed8ac811c2be0f01 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:14:56 -0400 Subject: [PATCH 036/120] Android: Convert InputMappingBooleanSetting to Kotlin --- .../model/InputMappingBooleanSetting.java | 50 ------------------- .../input/model/InputMappingBooleanSetting.kt | 25 ++++++++++ 2 files changed, 25 insertions(+), 50 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.java deleted file mode 100644 index 6e80fef6dd..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model; - -import androidx.annotation.NonNull; - -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting; -import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting; -import org.dolphinemu.dolphinemu.features.settings.model.Settings; - -public class InputMappingBooleanSetting implements AbstractBooleanSetting -{ - private final NumericSetting mNumericSetting; - - public InputMappingBooleanSetting(NumericSetting numericSetting) - { - mNumericSetting = numericSetting; - } - - @Override - public boolean getBoolean() - { - return mNumericSetting.getBooleanValue(); - } - - @Override - public void setBoolean(@NonNull Settings settings, boolean newValue) - { - mNumericSetting.setBooleanValue(newValue); - } - - @Override - public boolean isOverridden() - { - return false; - } - - @Override - public boolean isRuntimeEditable() - { - return true; - } - - @Override - public boolean delete(@NonNull Settings settings) - { - mNumericSetting.setBooleanValue(mNumericSetting.getBooleanDefaultValue()); - return true; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.kt new file mode 100644 index 0000000000..5771db54e0 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingBooleanSetting.kt @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting +import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting +import org.dolphinemu.dolphinemu.features.settings.model.Settings + +class InputMappingBooleanSetting(private val numericSetting: NumericSetting) : + AbstractBooleanSetting { + override val boolean: Boolean + get() = numericSetting.getBooleanValue() + + override fun setBoolean(settings: Settings, newValue: Boolean) = + numericSetting.setBooleanValue(newValue) + + override val isOverridden: Boolean = false + + override val isRuntimeEditable: Boolean = true + + override fun delete(settings: Settings): Boolean { + numericSetting.setBooleanValue(numericSetting.getBooleanDefaultValue()) + return true + } +} From 60b3b1231ba4a706801d8b300575383e6b13089b Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:15:11 -0400 Subject: [PATCH 037/120] Android: Convert InputMappingDoubleSetting to Kotlin --- .../model/InputMappingDoubleSetting.java | 51 ------------------- .../input/model/InputMappingDoubleSetting.kt | 25 +++++++++ 2 files changed, 25 insertions(+), 51 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.java deleted file mode 100644 index 0311e6d2b3..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.java +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model; - -import androidx.annotation.NonNull; - -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting; -import org.dolphinemu.dolphinemu.features.settings.model.AbstractFloatSetting; -import org.dolphinemu.dolphinemu.features.settings.model.Settings; - -// Yes, floats are not the same thing as doubles... They're close enough, though -public class InputMappingDoubleSetting implements AbstractFloatSetting -{ - private final NumericSetting mNumericSetting; - - public InputMappingDoubleSetting(NumericSetting numericSetting) - { - mNumericSetting = numericSetting; - } - - @Override - public float getFloat() - { - return (float) mNumericSetting.getDoubleValue(); - } - - @Override - public void setFloat(@NonNull Settings settings, float newValue) - { - mNumericSetting.setDoubleValue(newValue); - } - - @Override - public boolean isOverridden() - { - return false; - } - - @Override - public boolean isRuntimeEditable() - { - return true; - } - - @Override - public boolean delete(@NonNull Settings settings) - { - mNumericSetting.setDoubleValue(mNumericSetting.getDoubleDefaultValue()); - return true; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.kt new file mode 100644 index 0000000000..e2da062250 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingDoubleSetting.kt @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting +import org.dolphinemu.dolphinemu.features.settings.model.AbstractFloatSetting +import org.dolphinemu.dolphinemu.features.settings.model.Settings + +// Yes, floats are not the same thing as doubles... They're close enough, though +class InputMappingDoubleSetting(private val numericSetting: NumericSetting) : AbstractFloatSetting { + override val float: Float + get() = numericSetting.getDoubleValue().toFloat() + + override fun setFloat(settings: Settings, newValue: Float) = + numericSetting.setDoubleValue(newValue.toDouble()) + + override val isOverridden: Boolean = false + + override val isRuntimeEditable: Boolean = true + + override fun delete(settings: Settings): Boolean { + numericSetting.setDoubleValue(numericSetting.getDoubleDefaultValue()) + return true + } +} From cb9c670d85f3d35c9c5be9207d4a203c300d8783 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:15:23 -0400 Subject: [PATCH 038/120] Android: Convert InputMappingIntSetting to Kotlin --- .../input/model/InputMappingIntSetting.java | 50 ------------------- .../input/model/InputMappingIntSetting.kt | 23 +++++++++ 2 files changed, 23 insertions(+), 50 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.java deleted file mode 100644 index 176346c63a..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.java +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model; - -import androidx.annotation.NonNull; - -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting; -import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting; -import org.dolphinemu.dolphinemu.features.settings.model.Settings; - -public class InputMappingIntSetting implements AbstractIntSetting -{ - private final NumericSetting mNumericSetting; - - public InputMappingIntSetting(NumericSetting numericSetting) - { - mNumericSetting = numericSetting; - } - - @Override - public int getInt() - { - return mNumericSetting.getIntValue(); - } - - @Override - public void setInt(@NonNull Settings settings, int newValue) - { - mNumericSetting.setIntValue(newValue); - } - - @Override - public boolean isOverridden() - { - return false; - } - - @Override - public boolean isRuntimeEditable() - { - return true; - } - - @Override - public boolean delete(@NonNull Settings settings) - { - mNumericSetting.setIntValue(mNumericSetting.getIntDefaultValue()); - return true; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.kt new file mode 100644 index 0000000000..3e8a14afc7 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputMappingIntSetting.kt @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.NumericSetting +import org.dolphinemu.dolphinemu.features.settings.model.AbstractIntSetting +import org.dolphinemu.dolphinemu.features.settings.model.Settings + +class InputMappingIntSetting(private val numericSetting: NumericSetting) : AbstractIntSetting { + override val int: Int + get() = numericSetting.getIntValue() + + override fun setInt(settings: Settings, newValue: Int) = numericSetting.setIntValue(newValue) + + override val isOverridden: Boolean = false + + override val isRuntimeEditable: Boolean = true + + override fun delete(settings: Settings): Boolean { + numericSetting.setIntValue(numericSetting.getIntDefaultValue()) + return true + } +} From f8ab65bcaca3d830ecb66ae21b327f4031484f7a Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:15:38 -0400 Subject: [PATCH 039/120] Android: Convert InputOverrider to Kotlin --- .../features/input/model/InputOverrider.java | 84 ------------------- .../features/input/model/InputOverrider.kt | 82 ++++++++++++++++++ 2 files changed, 82 insertions(+), 84 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.java deleted file mode 100644 index 6596c8dd91..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.java +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model; - -public final class InputOverrider -{ - public static final class ControlId - { - public static final int GCPAD_A_BUTTON = 0; - public static final int GCPAD_B_BUTTON = 1; - public static final int GCPAD_X_BUTTON = 2; - public static final int GCPAD_Y_BUTTON = 3; - public static final int GCPAD_Z_BUTTON = 4; - public static final int GCPAD_START_BUTTON = 5; - public static final int GCPAD_DPAD_UP = 6; - public static final int GCPAD_DPAD_DOWN = 7; - public static final int GCPAD_DPAD_LEFT = 8; - public static final int GCPAD_DPAD_RIGHT = 9; - public static final int GCPAD_L_DIGITAL = 10; - public static final int GCPAD_R_DIGITAL = 11; - public static final int GCPAD_L_ANALOG = 12; - public static final int GCPAD_R_ANALOG = 13; - public static final int GCPAD_MAIN_STICK_X = 14; - public static final int GCPAD_MAIN_STICK_Y = 15; - public static final int GCPAD_C_STICK_X = 16; - public static final int GCPAD_C_STICK_Y = 17; - - public static final int WIIMOTE_A_BUTTON = 18; - public static final int WIIMOTE_B_BUTTON = 19; - public static final int WIIMOTE_ONE_BUTTON = 20; - public static final int WIIMOTE_TWO_BUTTON = 21; - public static final int WIIMOTE_PLUS_BUTTON = 22; - public static final int WIIMOTE_MINUS_BUTTON = 23; - public static final int WIIMOTE_HOME_BUTTON = 24; - public static final int WIIMOTE_DPAD_UP = 25; - public static final int WIIMOTE_DPAD_DOWN = 26; - public static final int WIIMOTE_DPAD_LEFT = 27; - public static final int WIIMOTE_DPAD_RIGHT = 28; - public static final int WIIMOTE_IR_X = 29; - public static final int WIIMOTE_IR_Y = 30; - - public static final int NUNCHUK_C_BUTTON = 31; - public static final int NUNCHUK_Z_BUTTON = 32; - public static final int NUNCHUK_STICK_X = 33; - public static final int NUNCHUK_STICK_Y = 34; - - public static final int CLASSIC_A_BUTTON = 35; - public static final int CLASSIC_B_BUTTON = 36; - public static final int CLASSIC_X_BUTTON = 37; - public static final int CLASSIC_Y_BUTTON = 38; - public static final int CLASSIC_ZL_BUTTON = 39; - public static final int CLASSIC_ZR_BUTTON = 40; - public static final int CLASSIC_PLUS_BUTTON = 41; - public static final int CLASSIC_MINUS_BUTTON = 42; - public static final int CLASSIC_HOME_BUTTON = 43; - public static final int CLASSIC_DPAD_UP = 44; - public static final int CLASSIC_DPAD_DOWN = 45; - public static final int CLASSIC_DPAD_LEFT = 46; - public static final int CLASSIC_DPAD_RIGHT = 47; - public static final int CLASSIC_L_DIGITAL = 48; - public static final int CLASSIC_R_DIGITAL = 49; - public static final int CLASSIC_L_ANALOG = 50; - public static final int CLASSIC_R_ANALOG = 51; - public static final int CLASSIC_LEFT_STICK_X = 52; - public static final int CLASSIC_LEFT_STICK_Y = 53; - public static final int CLASSIC_RIGHT_STICK_X = 54; - public static final int CLASSIC_RIGHT_STICK_Y = 55; - } - - public static native void registerGameCube(int controllerIndex); - - public static native void registerWii(int controllerIndex); - - public static native void unregisterGameCube(int controllerIndex); - - public static native void unregisterWii(int controllerIndex); - - public static native void setControlState(int controllerIndex, int control, double state); - - public static native void clearControlState(int controllerIndex, int control); - - // Angle is in radians and should be non-negative - public static native double getGateRadiusAtAngle(int emuPadId, int stick, double angle); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.kt new file mode 100644 index 0000000000..8224434ee6 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/InputOverrider.kt @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +object InputOverrider { + external fun registerGameCube(controllerIndex: Int) + + external fun registerWii(controllerIndex: Int) + + external fun unregisterGameCube(controllerIndex: Int) + + external fun unregisterWii(controllerIndex: Int) + + external fun setControlState(controllerIndex: Int, control: Int, state: Double) + + external fun clearControlState(controllerIndex: Int, control: Int) + + // Angle is in radians and should be non-negative + external fun getGateRadiusAtAngle(emuPadId: Int, stick: Int, angle: Double): Double + + object ControlId { + const val GCPAD_A_BUTTON = 0 + const val GCPAD_B_BUTTON = 1 + const val GCPAD_X_BUTTON = 2 + const val GCPAD_Y_BUTTON = 3 + const val GCPAD_Z_BUTTON = 4 + const val GCPAD_START_BUTTON = 5 + const val GCPAD_DPAD_UP = 6 + const val GCPAD_DPAD_DOWN = 7 + const val GCPAD_DPAD_LEFT = 8 + const val GCPAD_DPAD_RIGHT = 9 + const val GCPAD_L_DIGITAL = 10 + const val GCPAD_R_DIGITAL = 11 + const val GCPAD_L_ANALOG = 12 + const val GCPAD_R_ANALOG = 13 + const val GCPAD_MAIN_STICK_X = 14 + const val GCPAD_MAIN_STICK_Y = 15 + const val GCPAD_C_STICK_X = 16 + const val GCPAD_C_STICK_Y = 17 + + const val WIIMOTE_A_BUTTON = 18 + const val WIIMOTE_B_BUTTON = 19 + const val WIIMOTE_ONE_BUTTON = 20 + const val WIIMOTE_TWO_BUTTON = 21 + const val WIIMOTE_PLUS_BUTTON = 22 + const val WIIMOTE_MINUS_BUTTON = 23 + const val WIIMOTE_HOME_BUTTON = 24 + const val WIIMOTE_DPAD_UP = 25 + const val WIIMOTE_DPAD_DOWN = 26 + const val WIIMOTE_DPAD_LEFT = 27 + const val WIIMOTE_DPAD_RIGHT = 28 + const val WIIMOTE_IR_X = 29 + const val WIIMOTE_IR_Y = 30 + + const val NUNCHUK_C_BUTTON = 31 + const val NUNCHUK_Z_BUTTON = 32 + const val NUNCHUK_STICK_X = 33 + const val NUNCHUK_STICK_Y = 34 + + const val CLASSIC_A_BUTTON = 35 + const val CLASSIC_B_BUTTON = 36 + const val CLASSIC_X_BUTTON = 37 + const val CLASSIC_Y_BUTTON = 38 + const val CLASSIC_ZL_BUTTON = 39 + const val CLASSIC_ZR_BUTTON = 40 + const val CLASSIC_PLUS_BUTTON = 41 + const val CLASSIC_MINUS_BUTTON = 42 + const val CLASSIC_HOME_BUTTON = 43 + const val CLASSIC_DPAD_UP = 44 + const val CLASSIC_DPAD_DOWN = 45 + const val CLASSIC_DPAD_LEFT = 46 + const val CLASSIC_DPAD_RIGHT = 47 + const val CLASSIC_L_DIGITAL = 48 + const val CLASSIC_R_DIGITAL = 49 + const val CLASSIC_L_ANALOG = 50 + const val CLASSIC_R_ANALOG = 51 + const val CLASSIC_LEFT_STICK_X = 52 + const val CLASSIC_LEFT_STICK_Y = 53 + const val CLASSIC_RIGHT_STICK_X = 54 + const val CLASSIC_RIGHT_STICK_Y = 55 + } +} From 2590382871c0e4000577434fcfa79d7d7393200f Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:15:49 -0400 Subject: [PATCH 040/120] Android: Convert MappingCommon to Kotlin --- .../features/input/model/MappingCommon.java | 34 ------------------- .../features/input/model/MappingCommon.kt | 29 ++++++++++++++++ 2 files changed, 29 insertions(+), 34 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java deleted file mode 100644 index 5e4c0076ea..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.java +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model; - -import androidx.annotation.NonNull; - -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; - -public final class MappingCommon -{ - private MappingCommon() - { - } - - /** - * Waits until the user presses one or more inputs or until a timeout, - * then returns the pressed inputs. - * - * When this is being called, a separate thread must be calling ControllerInterface's - * dispatchKeyEvent and dispatchGenericMotionEvent, otherwise no inputs will be registered. - * - * @param controller The device to detect inputs from. - * @param allDevices Whether to also detect inputs from devices other than the specified one. - * @return The input(s) pressed by the user in the form of an InputCommon expression, - * or an empty string if there were no inputs. - */ - public static native String detectInput(@NonNull EmulatedController controller, - boolean allDevices); - - public static native String getExpressionForControl(String control, String device, - String defaultDevice); - - public static native void save(); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.kt new file mode 100644 index 0000000000..32775a7ebc --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/MappingCommon.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController + +object MappingCommon { + /** + * Waits until the user presses one or more inputs or until a timeout, + * then returns the pressed inputs. + * + * When this is being called, a separate thread must be calling ControllerInterface's + * dispatchKeyEvent and dispatchGenericMotionEvent, otherwise no inputs will be registered. + * + * @param controller The device to detect inputs from. + * @param allDevices Whether to also detect inputs from devices other than the specified one. + * @return The input(s) pressed by the user in the form of an InputCommon expression, + * or an empty string if there were no inputs. + */ + external fun detectInput(controller: EmulatedController, allDevices: Boolean): String + + external fun getExpressionForControl( + control: String, + device: String, + defaultDevice: String + ): String + + external fun save() +} From 9d7bd6e6bd4deb80c749027b3a7c59717d154c8a Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:16:07 -0400 Subject: [PATCH 041/120] Android: Convert InputDeviceSetting to Kotlin --- .../input/model/view/InputDeviceSetting.java | 70 ------------------- .../input/model/view/InputDeviceSetting.kt | 42 +++++++++++ .../features/settings/model/PostProcessing.kt | 6 +- .../model/view/StringSingleChoiceSetting.kt | 16 ++--- .../features/settings/ui/SettingsAdapter.kt | 2 +- .../settings/ui/SettingsFragmentPresenter.kt | 9 +-- 6 files changed, 56 insertions(+), 89 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java deleted file mode 100644 index f02de2e3f3..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.java +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model.view; - -import android.content.Context; - -import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface; -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; -import org.dolphinemu.dolphinemu.features.settings.model.Settings; -import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting; - -public class InputDeviceSetting extends StringSingleChoiceSetting -{ - private final EmulatedController mController; - - public InputDeviceSetting(Context context, int titleId, int descriptionId, - EmulatedController controller) - { - super(context, null, titleId, descriptionId, null, null, null); - - mController = controller; - - refreshChoicesAndValues(); - } - - @Override - public String getSelectedChoice() - { - return mController.getDefaultDevice(); - } - - @Override - public String getSelectedValue() - { - return mController.getDefaultDevice(); - } - - @Override - public void setSelectedValue(Settings settings, String newValue) - { - mController.setDefaultDevice(newValue); - } - - @Override - public void refreshChoicesAndValues() - { - String[] devices = ControllerInterface.getAllDeviceStrings(); - - setChoices(devices); - setValues(devices); - } - - @Override - public boolean isEditable() - { - return true; - } - - @Override - public boolean canClear() - { - return true; - } - - @Override - public void clear(Settings settings) - { - setSelectedValue(settings, ""); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.kt new file mode 100644 index 0000000000..c98af542c7 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.kt @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.view + +import android.content.Context +import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController +import org.dolphinemu.dolphinemu.features.settings.model.Settings +import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting + +class InputDeviceSetting( + context: Context, + titleId: Int, + descriptionId: Int, + private val controller: EmulatedController +) : StringSingleChoiceSetting(context, null, titleId, descriptionId, null, null, null) { + init { + refreshChoicesAndValues() + } + + override val selectedChoice: String + get() = controller.getDefaultDevice() + + override val selectedValue: String + get() = controller.getDefaultDevice() + + override fun setSelectedValue(settings: Settings, selection: String) = + controller.setDefaultDevice(selection) + + override fun refreshChoicesAndValues() { + val devices = ControllerInterface.getAllDeviceStrings() + + choices = devices + values = devices + } + + override val isEditable: Boolean = true + + override fun canClear(): Boolean = true + + override fun clear(settings: Settings) = setSelectedValue(settings, "") +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/PostProcessing.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/PostProcessing.kt index 1bb1aa04d7..04fe2734bb 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/PostProcessing.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/PostProcessing.kt @@ -4,14 +4,14 @@ package org.dolphinemu.dolphinemu.features.settings.model object PostProcessing { @JvmStatic - val shaderList: Array + val shaderList: Array external get @JvmStatic - val anaglyphShaderList: Array + val anaglyphShaderList: Array external get @JvmStatic - val passiveShaderList: Array + val passiveShaderList: Array external get } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.kt index 62301e23ef..a67992ce2b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.kt @@ -17,9 +17,9 @@ open class StringSingleChoiceSetting : SettingsItem { override val setting: AbstractSetting? get() = stringSetting - var choices: Array? + var choices: Array? protected set - var values: Array? + var values: Array? protected set val menuTag: MenuTag? var noChoicesAvailableString = 0 @@ -37,8 +37,8 @@ open class StringSingleChoiceSetting : SettingsItem { setting: AbstractStringSetting?, titleId: Int, descriptionId: Int, - choices: Array?, - values: Array?, + choices: Array?, + values: Array?, menuTag: MenuTag? = null ) : super(context, titleId, descriptionId) { stringSetting = setting @@ -52,8 +52,8 @@ open class StringSingleChoiceSetting : SettingsItem { setting: AbstractStringSetting, titleId: Int, descriptionId: Int, - choices: Array, - values: Array, + choices: Array, + values: Array, noChoicesAvailableString: Int ) : this(context, setting, titleId, descriptionId, choices, values) { this.noChoicesAvailableString = noChoicesAvailableString @@ -102,8 +102,8 @@ open class StringSingleChoiceSetting : SettingsItem { return -1 } - open fun setSelectedValue(settings: Settings?, selection: String?) { - stringSetting!!.setString(settings!!, selection!!) + open fun setSelectedValue(settings: Settings, selection: String) { + stringSetting!!.setString(settings, selection) } open fun refreshChoicesAndValues() {} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt index e3e745fafa..3623f167bb 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt @@ -474,7 +474,7 @@ class SettingsAdapter( val value = scSetting.getValueAt(which) if (scSetting.selectedValue != value) fragmentView.onSettingChanged() - scSetting.setSelectedValue(settings, value) + scSetting.setSelectedValue(settings!!, value!!) closeDialog() } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 2ec27cff72..f339f7538c 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -1339,13 +1339,8 @@ class SettingsFragmentPresenter( val shaderList = if (stereoModeValue == anaglyphMode) PostProcessing.anaglyphShaderList else PostProcessing.shaderList - val shaderListEntries = arrayOfNulls(shaderList.size + 1) - shaderListEntries[0] = context.getString(R.string.off) - System.arraycopy(shaderList, 0, shaderListEntries, 1, shaderList.size) - - val shaderListValues = arrayOfNulls(shaderList.size + 1) - shaderListValues[0] = "" - System.arraycopy(shaderList, 0, shaderListValues, 1, shaderList.size) + val shaderListEntries = arrayOf(context.getString(R.string.off), *shaderList) + val shaderListValues = arrayOf("", *shaderList) sl.add( StringSingleChoiceSetting( From d21b2d86a8eb16b2f9575ee85a4cbc4ef2d489a0 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:16:27 -0400 Subject: [PATCH 042/120] Android: Convert InputMappingControlSetting to Kotlin --- .../view/InputMappingControlSetting.java | 71 ------------------- .../model/view/InputMappingControlSetting.kt | 33 +++++++++ 2 files changed, 33 insertions(+), 71 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java deleted file mode 100644 index 86379bf5a6..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.java +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.model.view; - -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.Control; -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlReference; -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; -import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting; -import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem; - -public final class InputMappingControlSetting extends SettingsItem -{ - private final ControlReference mControlReference; - private final EmulatedController mController; - - public InputMappingControlSetting(Control control, EmulatedController controller) - { - super(control.getUiName(), ""); - mControlReference = control.getControlReference(); - mController = controller; - } - - public String getValue() - { - return mControlReference.getExpression(); - } - - public void setValue(String expr) - { - mControlReference.setExpression(expr); - mController.updateSingleControlReference(mControlReference); - } - - public void clearValue() - { - setValue(""); - } - - @Override - public int getType() - { - return TYPE_INPUT_MAPPING_CONTROL; - } - - @Override - public AbstractSetting getSetting() - { - return null; - } - - @Override - public boolean isEditable() - { - return true; - } - - public EmulatedController getController() - { - return mController; - } - - public ControlReference getControlReference() - { - return mControlReference; - } - - public boolean isInput() - { - return mControlReference.isInput(); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.kt new file mode 100644 index 0000000000..26a86a9411 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputMappingControlSetting.kt @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.model.view + +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.Control +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController +import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting +import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem + +class InputMappingControlSetting(var control: Control, val controller: EmulatedController) : + SettingsItem(control.getUiName(), "") { + val controlReference get() = control.getControlReference() + + var value: String + get() = controlReference.getExpression() + set(expr) { + controlReference.setExpression(expr) + controller.updateSingleControlReference(controlReference) + } + + fun clearValue() { + value = "" + } + + override val type: Int = TYPE_INPUT_MAPPING_CONTROL + + override val setting: AbstractSetting? = null + + override val isEditable: Boolean = true + + val isInput: Boolean + get() = controlReference.isInput() +} From dac70351194294d1b9e7859a15d651d6961cb6b7 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:16:53 -0400 Subject: [PATCH 043/120] Android: Convert AdvancedMappingControlAdapter to Kotlin --- .../ui/AdvancedMappingControlAdapter.java | 55 ------------------- .../input/ui/AdvancedMappingControlAdapter.kt | 33 +++++++++++ 2 files changed, 33 insertions(+), 55 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.java deleted file mode 100644 index 0ad589fe40..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.java +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.ui; - -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding; - -import java.util.function.Consumer; - -public final class AdvancedMappingControlAdapter - extends RecyclerView.Adapter -{ - private final Consumer mOnClickCallback; - - private String[] mControls = new String[0]; - - public AdvancedMappingControlAdapter(Consumer onClickCallback) - { - mOnClickCallback = onClickCallback; - } - - @NonNull @Override - public AdvancedMappingControlViewHolder onCreateViewHolder(@NonNull ViewGroup parent, - int viewType) - { - LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - - ListItemAdvancedMappingControlBinding binding = - ListItemAdvancedMappingControlBinding.inflate(inflater); - return new AdvancedMappingControlViewHolder(binding, mOnClickCallback); - } - - @Override - public void onBindViewHolder(@NonNull AdvancedMappingControlViewHolder holder, int position) - { - holder.bind(mControls[position]); - } - - @Override - public int getItemCount() - { - return mControls.length; - } - - public void setControls(String[] controls) - { - mControls = controls; - notifyDataSetChanged(); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.kt new file mode 100644 index 0000000000..1c82616726 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlAdapter.kt @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding +import java.util.function.Consumer + +class AdvancedMappingControlAdapter(private val onClickCallback: Consumer) : + RecyclerView.Adapter() { + private var controls = emptyArray() + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): AdvancedMappingControlViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ListItemAdvancedMappingControlBinding.inflate(inflater) + return AdvancedMappingControlViewHolder(binding, onClickCallback) + } + + override fun onBindViewHolder(holder: AdvancedMappingControlViewHolder, position: Int) = + holder.bind(controls[position]) + + override fun getItemCount(): Int = controls.size + + fun setControls(controls: Array) { + this.controls = controls + notifyDataSetChanged() + } +} From d049be0cad2643623d664fb52922ec1b1d289c97 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:17:10 -0400 Subject: [PATCH 044/120] Android: Convert AdvancedMappingControlViewHolder to Kotlin --- .../ui/AdvancedMappingControlViewHolder.java | 34 ------------------- .../ui/AdvancedMappingControlViewHolder.kt | 23 +++++++++++++ 2 files changed, 23 insertions(+), 34 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.java deleted file mode 100644 index 2ce925235b..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.java +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.ui; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding; - -import java.util.function.Consumer; - -public class AdvancedMappingControlViewHolder extends RecyclerView.ViewHolder -{ - private final ListItemAdvancedMappingControlBinding mBinding; - - private String mName; - - public AdvancedMappingControlViewHolder(@NonNull ListItemAdvancedMappingControlBinding binding, - Consumer onClickCallback) - { - super(binding.getRoot()); - - mBinding = binding; - - binding.getRoot().setOnClickListener(view -> onClickCallback.accept(mName)); - } - - public void bind(String name) - { - mName = name; - - mBinding.textName.setText(name); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.kt new file mode 100644 index 0000000000..dbfecfc94d --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingControlViewHolder.kt @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui + +import androidx.recyclerview.widget.RecyclerView +import org.dolphinemu.dolphinemu.databinding.ListItemAdvancedMappingControlBinding +import java.util.function.Consumer + +class AdvancedMappingControlViewHolder( + private val binding: ListItemAdvancedMappingControlBinding, + onClickCallback: Consumer +) : RecyclerView.ViewHolder(binding.root) { + private lateinit var name: String + + init { + binding.root.setOnClickListener { onClickCallback.accept(name) } + } + + fun bind(name: String) { + this.name = name + binding.textName.text = name + } +} From 5171290bdb3483cdf4c878b16940844abaa821ef Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:17:23 -0400 Subject: [PATCH 045/120] Android: Convert AdvancedMappingDialog to Kotlin --- .../input/ui/AdvancedMappingDialog.java | 151 ------------------ .../input/ui/AdvancedMappingDialog.kt | 122 ++++++++++++++ 2 files changed, 122 insertions(+), 151 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.java deleted file mode 100644 index de8e8265a6..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.java +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.ui; - -import android.content.Context; -import android.view.View; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; - -import androidx.appcompat.app.AlertDialog; -import androidx.recyclerview.widget.LinearLayoutManager; - -import com.google.android.material.divider.MaterialDividerItemDecoration; - -import org.dolphinemu.dolphinemu.databinding.DialogAdvancedMappingBinding; -import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface; -import org.dolphinemu.dolphinemu.features.input.model.CoreDevice; -import org.dolphinemu.dolphinemu.features.input.model.MappingCommon; -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlReference; -import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController; - -import java.util.Arrays; -import java.util.Optional; - -public final class AdvancedMappingDialog extends AlertDialog - implements AdapterView.OnItemClickListener -{ - private final DialogAdvancedMappingBinding mBinding; - private final ControlReference mControlReference; - private final EmulatedController mController; - private final String[] mDevices; - private final AdvancedMappingControlAdapter mControlAdapter; - - private String mSelectedDevice; - - public AdvancedMappingDialog(Context context, DialogAdvancedMappingBinding binding, - ControlReference controlReference, EmulatedController controller) - { - super(context); - - mBinding = binding; - mControlReference = controlReference; - mController = controller; - - mDevices = ControllerInterface.getAllDeviceStrings(); - - // TODO: Remove workaround for text filtering issue in material components when fixed - // https://github.com/material-components/material-components-android/issues/1464 - mBinding.dropdownDevice.setSaveEnabled(false); - - binding.dropdownDevice.setOnItemClickListener(this); - - ArrayAdapter deviceAdapter = new ArrayAdapter<>( - context, android.R.layout.simple_spinner_dropdown_item, mDevices); - binding.dropdownDevice.setAdapter(deviceAdapter); - - mControlAdapter = new AdvancedMappingControlAdapter(this::onControlClicked); - mBinding.listControl.setAdapter(mControlAdapter); - mBinding.listControl.setLayoutManager(new LinearLayoutManager(context)); - - MaterialDividerItemDecoration divider = - new MaterialDividerItemDecoration(context, LinearLayoutManager.VERTICAL); - divider.setLastItemDecorated(false); - mBinding.listControl.addItemDecoration(divider); - - binding.editExpression.setText(controlReference.getExpression()); - - selectDefaultDevice(); - } - - public String getExpression() - { - return mBinding.editExpression.getText().toString(); - } - - @Override - public void onItemClick(AdapterView adapterView, View view, int position, long id) - { - setSelectedDevice(mDevices[position]); - } - - private void setSelectedDevice(String deviceString) - { - mSelectedDevice = deviceString; - - CoreDevice device = ControllerInterface.getDevice(deviceString); - if (device == null) - setControls(new CoreDevice.Control[0]); - else if (mControlReference.isInput()) - setControls(device.getInputs()); - else - setControls(device.getOutputs()); - } - - private void setControls(CoreDevice.Control[] controls) - { - mControlAdapter.setControls( - Arrays.stream(controls) - .map(CoreDevice.Control::getName) - .toArray(String[]::new)); - } - - private void onControlClicked(String control) - { - String expression = MappingCommon.getExpressionForControl(control, mSelectedDevice, - mController.getDefaultDevice()); - - int start = Math.max(mBinding.editExpression.getSelectionStart(), 0); - int end = Math.max(mBinding.editExpression.getSelectionEnd(), 0); - mBinding.editExpression.getText().replace( - Math.min(start, end), Math.max(start, end), expression, 0, expression.length()); - } - - private void selectDefaultDevice() - { - String defaultDevice = mController.getDefaultDevice(); - boolean isInput = mControlReference.isInput(); - - if (Arrays.asList(mDevices).contains(defaultDevice) && - (isInput || deviceHasOutputs(defaultDevice))) - { - // The default device is available, and it's an appropriate choice. Pick it - setSelectedDevice(defaultDevice); - mBinding.dropdownDevice.setText(defaultDevice, false); - return; - } - else if (!isInput) - { - // Find the first device that has an output. (Most built-in devices don't have any) - Optional deviceWithOutputs = Arrays.stream(mDevices) - .filter(AdvancedMappingDialog::deviceHasOutputs) - .findFirst(); - - if (deviceWithOutputs.isPresent()) - { - setSelectedDevice(deviceWithOutputs.get()); - mBinding.dropdownDevice.setText(deviceWithOutputs.get(), false); - return; - } - } - - // Nothing found - setSelectedDevice(""); - } - - private static boolean deviceHasOutputs(String deviceString) - { - CoreDevice device = ControllerInterface.getDevice(deviceString); - return device != null && device.getOutputs().length > 0; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.kt new file mode 100644 index 0000000000..72d08c26aa --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/AdvancedMappingDialog.kt @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui + +import android.content.Context +import android.view.View +import android.widget.AdapterView +import android.widget.AdapterView.OnItemClickListener +import android.widget.ArrayAdapter +import androidx.appcompat.app.AlertDialog +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.divider.MaterialDividerItemDecoration +import org.dolphinemu.dolphinemu.databinding.DialogAdvancedMappingBinding +import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface +import org.dolphinemu.dolphinemu.features.input.model.CoreDevice +import org.dolphinemu.dolphinemu.features.input.model.MappingCommon +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.ControlReference +import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController + +class AdvancedMappingDialog( + context: Context, + private val binding: DialogAdvancedMappingBinding, + private val controlReference: ControlReference, + private val controller: EmulatedController +) : AlertDialog(context), OnItemClickListener { + private val devices: Array = ControllerInterface.getAllDeviceStrings() + private val controlAdapter: AdvancedMappingControlAdapter + private lateinit var selectedDevice: String + + init { + // TODO: Remove workaround for text filtering issue in material components when fixed + // https://github.com/material-components/material-components-android/issues/1464 + binding.dropdownDevice.isSaveEnabled = false + + binding.dropdownDevice.onItemClickListener = this + + val deviceAdapter = + ArrayAdapter(context, android.R.layout.simple_spinner_dropdown_item, devices) + binding.dropdownDevice.setAdapter(deviceAdapter) + + controlAdapter = + AdvancedMappingControlAdapter { control: String -> onControlClicked(control) } + binding.listControl.adapter = controlAdapter + binding.listControl.layoutManager = LinearLayoutManager(context) + + val divider = MaterialDividerItemDecoration(context, LinearLayoutManager.VERTICAL) + divider.isLastItemDecorated = false + binding.listControl.addItemDecoration(divider) + + binding.editExpression.setText(controlReference.getExpression()) + + selectDefaultDevice() + } + + val expression: String + get() = binding.editExpression.text.toString() + + override fun onItemClick(adapterView: AdapterView<*>?, view: View, position: Int, id: Long) = + setSelectedDevice(devices[position]) + + private fun setSelectedDevice(deviceString: String) { + selectedDevice = deviceString + + val device = ControllerInterface.getDevice(deviceString) + if (device == null) + setControls(emptyArray()) + else if (controlReference.isInput()) + setControls(device.getInputs()) + else + setControls(device.getOutputs()) + } + + private fun setControls(controls: Array) = + controlAdapter.setControls(controls.map { it.getName() }.toTypedArray()) + + private fun onControlClicked(control: String) { + val expression = + MappingCommon.getExpressionForControl(control, selectedDevice, controller.getDefaultDevice()) + + val start = binding.editExpression.selectionStart.coerceAtLeast(0) + val end = binding.editExpression.selectionEnd.coerceAtLeast(0) + binding.editExpression.text?.replace( + start.coerceAtMost(end), + start.coerceAtLeast(end), + expression, + 0, + expression.length + ) + } + + private fun selectDefaultDevice() { + val defaultDevice = controller.getDefaultDevice() + val isInput = controlReference.isInput() + + if (listOf(*devices).contains(defaultDevice) && + (isInput || deviceHasOutputs(defaultDevice)) + ) { + // The default device is available, and it's an appropriate choice. Pick it + setSelectedDevice(defaultDevice) + binding.dropdownDevice.setText(defaultDevice, false) + return + } else if (!isInput) { + // Find the first device that has an output. (Most built-in devices don't have any) + val deviceWithOutputs = devices.first { deviceHasOutputs(it) } + if (deviceWithOutputs.isNotEmpty()) { + setSelectedDevice(deviceWithOutputs) + binding.dropdownDevice.setText(deviceWithOutputs, false) + return + } + } + + // Nothing found + setSelectedDevice("") + } + + companion object { + private fun deviceHasOutputs(deviceString: String): Boolean { + val device = ControllerInterface.getDevice(deviceString) + return device != null && device.getOutputs().isNotEmpty() + } + } +} From dfafa74ba18cf65d68116d9b9f6729088c45f39b Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:17:36 -0400 Subject: [PATCH 046/120] Android: Convert MotionAlertDialog to Kotlin --- .../features/input/ui/MotionAlertDialog.java | 99 ------------------- .../features/input/ui/MotionAlertDialog.kt | 68 +++++++++++++ 2 files changed, 68 insertions(+), 99 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java deleted file mode 100644 index 9872bdd01e..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.java +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.ui; - -import android.app.Activity; -import android.view.InputDevice; -import android.view.KeyEvent; -import android.view.MotionEvent; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; - -import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface; -import org.dolphinemu.dolphinemu.features.input.model.MappingCommon; -import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting; - -/** - * {@link AlertDialog} derivative that listens for - * motion events from controllers and joysticks. - */ -public final class MotionAlertDialog extends AlertDialog -{ - private final Activity mActivity; - private final InputMappingControlSetting mSetting; - private final boolean mAllDevices; - private boolean mRunning = false; - - /** - * Constructor - * - * @param activity The current {@link Activity}. - * @param setting The setting to show this dialog for. - * @param allDevices Whether to detect inputs from devices other than the configured one. - */ - public MotionAlertDialog(Activity activity, InputMappingControlSetting setting, - boolean allDevices) - { - super(activity); - - mActivity = activity; - mSetting = setting; - mAllDevices = allDevices; - } - - @Override - protected void onStart() - { - super.onStart(); - - mRunning = true; - new Thread(() -> - { - String result = MappingCommon.detectInput(mSetting.getController(), mAllDevices); - mActivity.runOnUiThread(() -> - { - if (mRunning) - { - mSetting.setValue(result); - dismiss(); - } - }); - }).start(); - } - - @Override - protected void onStop() - { - super.onStop(); - mRunning = false; - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) - { - ControllerInterface.dispatchKeyEvent(event); - - if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.isLongPress()) - { - // Special case: Let the user cancel by long-pressing Back (intended for non-touch devices) - mSetting.clearValue(); - dismiss(); - } - - return true; - } - - @Override - public boolean dispatchGenericMotionEvent(@NonNull MotionEvent event) - { - if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) - { - // Special case: Let the user cancel by touching an on-screen button - return super.dispatchGenericMotionEvent(event); - } - - ControllerInterface.dispatchGenericMotionEvent(event); - return true; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.kt new file mode 100644 index 0000000000..ff4a278f4c --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/MotionAlertDialog.kt @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui + +import android.app.Activity +import android.view.InputDevice +import android.view.KeyEvent +import android.view.MotionEvent +import androidx.appcompat.app.AlertDialog +import org.dolphinemu.dolphinemu.features.input.model.ControllerInterface +import org.dolphinemu.dolphinemu.features.input.model.MappingCommon +import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting + +/** + * [AlertDialog] derivative that listens for + * motion events from controllers and joysticks. + * + * @param activity The current [Activity]. + * @param setting The setting to show this dialog for. + * @param allDevices Whether to detect inputs from devices other than the configured one. + */ +class MotionAlertDialog( + private val activity: Activity, + private val setting: InputMappingControlSetting, + private val allDevices: Boolean +) : AlertDialog(activity) { + private var running = false + + override fun onStart() { + super.onStart() + + running = true + Thread { + val result = MappingCommon.detectInput(setting.controller, allDevices) + activity.runOnUiThread { + if (running) { + setting.value = result + dismiss() + } + } + }.start() + } + + override fun onStop() { + super.onStop() + running = false + } + + override fun dispatchKeyEvent(event: KeyEvent): Boolean { + ControllerInterface.dispatchKeyEvent(event) + if (event.keyCode == KeyEvent.KEYCODE_BACK && event.isLongPress) { + // Special case: Let the user cancel by long-pressing Back (intended for non-touch devices) + setting.clearValue() + dismiss() + } + return true + } + + override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean { + if (event.source and InputDevice.SOURCE_CLASS_POINTER != 0) { + // Special case: Let the user cancel by touching an on-screen button + return super.dispatchGenericMotionEvent(event) + } + + ControllerInterface.dispatchGenericMotionEvent(event) + return true + } +} From 6ad18e4ee59688bb3ee6aaab73073dd92dbce4c2 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:17:49 -0400 Subject: [PATCH 047/120] Android: Convert ProfileAdapter to Kotlin --- .../features/input/ui/ProfileAdapter.java | 65 ------------------- .../features/input/ui/ProfileAdapter.kt | 42 ++++++++++++ .../features/input/ui/ProfileDialog.kt | 6 +- 3 files changed, 45 insertions(+), 68 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.java deleted file mode 100644 index 9908d6f5fd..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.java +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.ui; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding; - -public final class ProfileAdapter extends RecyclerView.Adapter -{ - private final Context mContext; - private final ProfileDialogPresenter mPresenter; - - private final String[] mStockProfileNames; - private final String[] mUserProfileNames; - - public ProfileAdapter(Context context, ProfileDialogPresenter presenter) - { - mContext = context; - mPresenter = presenter; - - mStockProfileNames = presenter.getProfileNames(true); - mUserProfileNames = presenter.getProfileNames(false); - } - - @NonNull @Override - public ProfileViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) - { - LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - - ListItemProfileBinding binding = ListItemProfileBinding.inflate(inflater, parent, false); - return new ProfileViewHolder(mPresenter, binding); - } - - @Override - public void onBindViewHolder(@NonNull ProfileViewHolder holder, int position) - { - if (position < mStockProfileNames.length) - { - holder.bind(mStockProfileNames[position], true); - return; - } - - position -= mStockProfileNames.length; - - if (position < mUserProfileNames.length) - { - holder.bind(mUserProfileNames[position], false); - return; - } - - holder.bindAsEmpty(mContext); - } - - @Override - public int getItemCount() - { - return mStockProfileNames.length + mUserProfileNames.length + 1; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.kt new file mode 100644 index 0000000000..71aa166cc9 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileAdapter.kt @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding + +class ProfileAdapter( + private val context: Context, + private val presenter: ProfileDialogPresenter +) : RecyclerView.Adapter() { + private val stockProfileNames: Array = presenter.getProfileNames(true) + private val userProfileNames: Array = presenter.getProfileNames(false) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ListItemProfileBinding.inflate(inflater, parent, false) + return ProfileViewHolder(presenter, binding) + } + + override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) { + var profilePosition = position + if (profilePosition < stockProfileNames.size) { + holder.bind(stockProfileNames[profilePosition], true) + return + } + + profilePosition -= stockProfileNames.size + + if (profilePosition < userProfileNames.size) { + holder.bind(userProfileNames[profilePosition], false) + return + } + + holder.bindAsEmpty(context) + } + + override fun getItemCount(): Int = stockProfileNames.size + userProfileNames.size + 1 +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialog.kt index acc3f605a9..4ef3dae51f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialog.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialog.kt @@ -16,13 +16,13 @@ import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable class ProfileDialog : BottomSheetDialogFragment() { - private var presenter: ProfileDialogPresenter? = null + private lateinit var presenter: ProfileDialogPresenter private var _binding: DialogInputProfilesBinding? = null private val binding get() = _binding!! override fun onCreate(savedInstanceState: Bundle?) { - val menuTag = requireArguments().serializable(KEY_MENU_TAG) + val menuTag = requireArguments().serializable(KEY_MENU_TAG)!! presenter = ProfileDialogPresenter(this, menuTag) @@ -39,7 +39,7 @@ class ProfileDialog : BottomSheetDialogFragment() { } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - binding.profileList.adapter = ProfileAdapter(context, presenter) + binding.profileList.adapter = ProfileAdapter(requireContext(), presenter) binding.profileList.layoutManager = LinearLayoutManager(context) val divider = MaterialDividerItemDecoration(requireActivity(), LinearLayoutManager.VERTICAL) divider.isLastItemDecorated = false From 29e5c78541bac86a126a1c35895ef8411566c7ed Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:18:08 -0400 Subject: [PATCH 048/120] Android: Convert ProfileDialogPresenter to Kotlin --- .../input/ui/ProfileDialogPresenter.java | 157 ------------------ .../input/ui/ProfileDialogPresenter.kt | 119 +++++++++++++ .../settings/ui/SettingsFragmentPresenter.kt | 10 +- 3 files changed, 124 insertions(+), 162 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.java deleted file mode 100644 index 667cc9017c..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.java +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.ui; - -import android.content.Context; -import android.view.LayoutInflater; - -import androidx.annotation.NonNull; -import androidx.fragment.app.DialogFragment; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.google.android.material.textfield.TextInputEditText; - -import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding; -import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag; -import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView; -import org.dolphinemu.dolphinemu.utils.DirectoryInitialization; - -import java.io.File; -import java.text.Collator; -import java.util.Arrays; - -public final class ProfileDialogPresenter -{ - private static final String EXTENSION = ".ini"; - - private final Context mContext; - private final DialogFragment mDialog; - private final MenuTag mMenuTag; - - public ProfileDialogPresenter(MenuTag menuTag) - { - mContext = null; - mDialog = null; - mMenuTag = menuTag; - } - - public ProfileDialogPresenter(DialogFragment dialog, MenuTag menuTag) - { - mContext = dialog.getContext(); - mDialog = dialog; - mMenuTag = menuTag; - } - - public String[] getProfileNames(boolean stock) - { - File[] profiles = new File(getProfileDirectoryPath(stock)).listFiles( - file -> !file.isDirectory() && file.getName().endsWith(EXTENSION)); - - if (profiles == null) - return new String[0]; - - return Arrays.stream(profiles) - .map(file -> file.getName().substring(0, file.getName().length() - EXTENSION.length())) - .sorted(Collator.getInstance()) - .toArray(String[]::new); - } - - public void loadProfile(@NonNull String profileName, boolean stock) - { - new MaterialAlertDialogBuilder(mContext) - .setMessage(mContext.getString(R.string.input_profile_confirm_load, profileName)) - .setPositiveButton(R.string.yes, (dialogInterface, i) -> - { - mMenuTag.getCorrespondingEmulatedController() - .loadProfile(getProfilePath(profileName, stock)); - ((SettingsActivityView) mDialog.requireActivity()).onControllerSettingsChanged(); - mDialog.dismiss(); - }) - .setNegativeButton(R.string.no, null) - .show(); - } - - public void saveProfile(@NonNull String profileName) - { - // If the user is saving over an existing profile, we should show an overwrite warning. - // If the user is creating a new profile, we normally shouldn't show a warning, - // but if they've entered the name of an existing profile, we should shown an overwrite warning. - - String profilePath = getProfilePath(profileName, false); - if (!new File(profilePath).exists()) - { - mMenuTag.getCorrespondingEmulatedController().saveProfile(profilePath); - mDialog.dismiss(); - } - else - { - new MaterialAlertDialogBuilder(mContext) - .setMessage(mContext.getString(R.string.input_profile_confirm_save, profileName)) - .setPositiveButton(R.string.yes, (dialogInterface, i) -> - { - mMenuTag.getCorrespondingEmulatedController().saveProfile(profilePath); - mDialog.dismiss(); - }) - .setNegativeButton(R.string.no, null) - .show(); - } - } - - public void saveProfileAndPromptForName() - { - LayoutInflater inflater = LayoutInflater.from(mContext); - - DialogInputStringBinding binding = DialogInputStringBinding.inflate(inflater); - TextInputEditText input = binding.input; - - new MaterialAlertDialogBuilder(mContext) - .setView(binding.getRoot()) - .setPositiveButton(R.string.ok, (dialogInterface, i) -> - saveProfile(input.getText().toString())) - .setNegativeButton(R.string.cancel, null) - .show(); - } - - public void deleteProfile(@NonNull String profileName) - { - new MaterialAlertDialogBuilder(mContext) - .setMessage(mContext.getString(R.string.input_profile_confirm_delete, profileName)) - .setPositiveButton(R.string.yes, (dialogInterface, i) -> - { - new File(getProfilePath(profileName, false)).delete(); - mDialog.dismiss(); - }) - .setNegativeButton(R.string.no, null) - .show(); - } - - private String getProfileDirectoryName() - { - if (mMenuTag.isGCPadMenu()) - return "GCPad"; - else if (mMenuTag.isWiimoteMenu()) - return "Wiimote"; - else - throw new UnsupportedOperationException(); - } - - private String getProfileDirectoryPath(boolean stock) - { - if (stock) - { - return DirectoryInitialization.getSysDirectory() + "/Profiles/" + getProfileDirectoryName() + - '/'; - } - else - { - return DirectoryInitialization.getUserDirectory() + "/Config/Profiles/" + - getProfileDirectoryName() + '/'; - } - } - - private String getProfilePath(String profileName, boolean stock) - { - return getProfileDirectoryPath(stock) + profileName + EXTENSION; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.kt new file mode 100644 index 0000000000..624441aa73 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileDialogPresenter.kt @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui + +import android.content.Context +import android.content.DialogInterface +import android.view.LayoutInflater +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.dolphinemu.dolphinemu.R +import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding +import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag +import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView +import org.dolphinemu.dolphinemu.utils.DirectoryInitialization +import java.io.File +import java.util.Locale + +class ProfileDialogPresenter { + private val context: Context? + private val dialog: DialogFragment? + private val menuTag: MenuTag + + constructor(menuTag: MenuTag) { + context = null + dialog = null + this.menuTag = menuTag + } + + constructor(dialog: DialogFragment, menuTag: MenuTag) { + context = dialog.context + this.dialog = dialog + this.menuTag = menuTag + } + + fun getProfileNames(stock: Boolean): Array { + val profiles = File(getProfileDirectoryPath(stock)).listFiles { file: File -> + !file.isDirectory && file.name.endsWith(EXTENSION) + } ?: return emptyArray() + + return profiles.map { it.name.substring(0, it.name.length - EXTENSION.length) } + .sortedBy { it.lowercase(Locale.getDefault()) } + .toTypedArray() + } + + fun loadProfile(profileName: String, stock: Boolean) { + MaterialAlertDialogBuilder(context!!) + .setMessage(context.getString(R.string.input_profile_confirm_load, profileName)) + .setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> + menuTag.correspondingEmulatedController + .loadProfile(getProfilePath(profileName, stock)) + (dialog!!.requireActivity() as SettingsActivityView).onControllerSettingsChanged() + dialog.dismiss() + } + .setNegativeButton(R.string.no, null) + .show() + } + + fun saveProfile(profileName: String) { + // If the user is saving over an existing profile, we should show an overwrite warning. + // If the user is creating a new profile, we normally shouldn't show a warning, + // but if they've entered the name of an existing profile, we should shown an overwrite warning. + val profilePath = getProfilePath(profileName, false) + if (!File(profilePath).exists()) { + menuTag.correspondingEmulatedController.saveProfile(profilePath) + dialog!!.dismiss() + } else { + MaterialAlertDialogBuilder(context!!) + .setMessage(context.getString(R.string.input_profile_confirm_save, profileName)) + .setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> + menuTag.correspondingEmulatedController.saveProfile(profilePath) + dialog!!.dismiss() + } + .setNegativeButton(R.string.no, null) + .show() + } + } + + fun saveProfileAndPromptForName() { + val inflater = LayoutInflater.from(context) + val binding = DialogInputStringBinding.inflate(inflater) + val input = binding.input + + MaterialAlertDialogBuilder(context!!) + .setView(binding.root) + .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> + saveProfile(input.text.toString()) + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + + fun deleteProfile(profileName: String) { + MaterialAlertDialogBuilder(context!!) + .setMessage(context.getString(R.string.input_profile_confirm_delete, profileName)) + .setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> + File(getProfilePath(profileName, false)).delete() + dialog!!.dismiss() + } + .setNegativeButton(R.string.no, null) + .show() + } + + private val profileDirectoryName: String + get() = if (menuTag.isGCPadMenu) "GCPad" else if (menuTag.isWiimoteMenu) "Wiimote" else throw UnsupportedOperationException() + + private fun getProfileDirectoryPath(stock: Boolean): String = + if (stock) { + "${DirectoryInitialization.getSysDirectory()}/Profiles/$profileDirectoryName/" + } else { + "${DirectoryInitialization.getUserDirectory()}/Config/Profiles/$profileDirectoryName/" + } + + private fun getProfilePath(profileName: String, stock: Boolean): String = + getProfileDirectoryPath(stock) + profileName + EXTENSION + + companion object { + private const val EXTENSION = ".ini" + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index f339f7538c..0947e4f294 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -41,7 +41,7 @@ class SettingsFragmentPresenter( private val fragmentView: SettingsFragmentView, private val context: Context ) { - private var menuTag: MenuTag? = null + private lateinit var menuTag: MenuTag private var gameId: String? = null private var settingsList: ArrayList? = null @@ -78,7 +78,7 @@ class SettingsFragmentPresenter( } } - fun onViewCreated(menuTag: MenuTag?, settings: Settings?) { + fun onViewCreated(menuTag: MenuTag, settings: Settings?) { this.menuTag = menuTag if (!TextUtils.isEmpty(gameId)) { @@ -2148,7 +2148,7 @@ class SettingsFragmentPresenter( profileString: String, controllerNumber: Int ) { - val profiles = ProfileDialogPresenter(menuTag!!).getProfileNames(false) + val profiles = ProfileDialogPresenter(menuTag).getProfileNames(false) val profileKey = profileString + "Profile" + (controllerNumber + 1) sl.add( StringSingleChoiceSetting( @@ -2227,7 +2227,7 @@ class SettingsFragmentPresenter( 0, 0, true - ) { fragmentView.showDialogFragment(ProfileDialog.create(menuTag!!)) }) + ) { fragmentView.showDialogFragment(ProfileDialog.create(menuTag)) }) updateOldControllerSettingsWarningVisibility(controller) } @@ -2313,7 +2313,7 @@ class SettingsFragmentPresenter( } fun updateOldControllerSettingsWarningVisibility() { - updateOldControllerSettingsWarningVisibility(menuTag!!.correspondingEmulatedController) + updateOldControllerSettingsWarningVisibility(menuTag.correspondingEmulatedController) } private fun updateOldControllerSettingsWarningVisibility(controller: EmulatedController) { From 6caa4307ac11f57ea4dcf41ea7d16d7029fa3800 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:18:37 -0400 Subject: [PATCH 049/120] Android: Convert ProfileViewHolder to Kotlin --- .../features/input/ui/ProfileViewHolder.java | 76 ------------------- .../features/input/ui/ProfileViewHolder.kt | 56 ++++++++++++++ 2 files changed, 56 insertions(+), 76 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.java deleted file mode 100644 index d4d40c0c65..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.java +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.ui; - -import android.content.Context; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding; - -public class ProfileViewHolder extends RecyclerView.ViewHolder -{ - private final ProfileDialogPresenter mPresenter; - private final ListItemProfileBinding mBinding; - - private String mProfileName; - private boolean mStock; - - public ProfileViewHolder(@NonNull ProfileDialogPresenter presenter, - @NonNull ListItemProfileBinding binding) - { - super(binding.getRoot()); - - mPresenter = presenter; - mBinding = binding; - - binding.buttonLoad.setOnClickListener(view -> loadProfile()); - binding.buttonSave.setOnClickListener(view -> saveProfile()); - binding.buttonDelete.setOnClickListener(view -> deleteProfile()); - } - - public void bind(String profileName, boolean stock) - { - mProfileName = profileName; - mStock = stock; - - mBinding.textName.setText(profileName); - - mBinding.buttonLoad.setVisibility(View.VISIBLE); - mBinding.buttonSave.setVisibility(stock ? View.GONE : View.VISIBLE); - mBinding.buttonDelete.setVisibility(stock ? View.GONE : View.VISIBLE); - } - - public void bindAsEmpty(Context context) - { - mProfileName = null; - mStock = false; - - mBinding.textName.setText(context.getText(R.string.input_profile_new)); - - mBinding.buttonLoad.setVisibility(View.GONE); - mBinding.buttonSave.setVisibility(View.VISIBLE); - mBinding.buttonDelete.setVisibility(View.GONE); - } - - private void loadProfile() - { - mPresenter.loadProfile(mProfileName, mStock); - } - - private void saveProfile() - { - if (mProfileName == null) - mPresenter.saveProfileAndPromptForName(); - else - mPresenter.saveProfile(mProfileName); - } - - private void deleteProfile() - { - mPresenter.deleteProfile(mProfileName); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.kt new file mode 100644 index 0000000000..671461c9aa --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/ProfileViewHolder.kt @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui + +import android.content.Context +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import org.dolphinemu.dolphinemu.R +import org.dolphinemu.dolphinemu.databinding.ListItemProfileBinding + +class ProfileViewHolder( + private val presenter: ProfileDialogPresenter, + private val binding: ListItemProfileBinding +) : RecyclerView.ViewHolder(binding.getRoot()) { + private var profileName: String? = null + private var stock = false + + init { + binding.buttonLoad.setOnClickListener { loadProfile() } + binding.buttonSave.setOnClickListener { saveProfile() } + binding.buttonDelete.setOnClickListener { deleteProfile() } + } + + fun bind(profileName: String, stock: Boolean) { + this.profileName = profileName + this.stock = stock + + binding.textName.text = profileName + + binding.buttonLoad.visibility = View.VISIBLE + binding.buttonSave.visibility = if (stock) View.GONE else View.VISIBLE + binding.buttonDelete.visibility = if (stock) View.GONE else View.VISIBLE + } + + fun bindAsEmpty(context: Context) { + profileName = null + stock = false + + binding.textName.text = context.getText(R.string.input_profile_new) + + binding.buttonLoad.visibility = View.GONE + binding.buttonSave.visibility = View.VISIBLE + binding.buttonDelete.visibility = View.GONE + } + + private fun loadProfile() = presenter.loadProfile(profileName!!, stock) + + private fun saveProfile() { + if (profileName == null) + presenter.saveProfileAndPromptForName() + else + presenter.saveProfile(profileName!!) + } + + private fun deleteProfile() = presenter.deleteProfile(profileName!!) +} From 0e3b33d90114e5d42fdc9f3fe6bb9fadc86eef4a Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 05:18:55 -0400 Subject: [PATCH 050/120] Android: Convert InputMappingControlSettingViewHolder to Kotlin --- .../InputMappingControlSettingViewHolder.java | 77 ------------------- .../InputMappingControlSettingViewHolder.kt | 55 +++++++++++++ 2 files changed, 55 insertions(+), 77 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.java deleted file mode 100644 index fd4b92cf95..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.java +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.input.ui.viewholder; - -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.dolphinemu.dolphinemu.databinding.ListItemMappingBinding; -import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting; -import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem; -import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter; -import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder; - -public final class InputMappingControlSettingViewHolder extends SettingViewHolder -{ - private InputMappingControlSetting mItem; - - private final ListItemMappingBinding mBinding; - - public InputMappingControlSettingViewHolder(@NonNull ListItemMappingBinding binding, - SettingsAdapter adapter) - { - super(binding.getRoot(), adapter); - mBinding = binding; - } - - @Override - public void bind(SettingsItem item) - { - mItem = (InputMappingControlSetting) item; - - mBinding.textSettingName.setText(mItem.getName()); - mBinding.textSettingDescription.setText(mItem.getValue()); - mBinding.buttonAdvancedSettings.setOnClickListener(this::onLongClick); - - setStyle(mBinding.textSettingName, mItem); - } - - @Override - public void onClick(View clicked) - { - if (!mItem.isEditable()) - { - showNotRuntimeEditableError(); - return; - } - - if (mItem.isInput()) - getAdapter().onInputMappingClick(mItem, getBindingAdapterPosition()); - else - getAdapter().onAdvancedInputMappingClick(mItem, getBindingAdapterPosition()); - - setStyle(mBinding.textSettingName, mItem); - } - - @Override - public boolean onLongClick(View clicked) - { - if (!mItem.isEditable()) - { - showNotRuntimeEditableError(); - return true; - } - - getAdapter().onAdvancedInputMappingClick(mItem, getBindingAdapterPosition()); - - return true; - } - - @Nullable @Override - protected SettingsItem getItem() - { - return mItem; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.kt new file mode 100644 index 0000000000..5bbcbf07c4 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/ui/viewholder/InputMappingControlSettingViewHolder.kt @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.input.ui.viewholder + +import android.view.View +import org.dolphinemu.dolphinemu.databinding.ListItemMappingBinding +import org.dolphinemu.dolphinemu.features.input.model.view.InputMappingControlSetting +import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem +import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter +import org.dolphinemu.dolphinemu.features.settings.ui.viewholder.SettingViewHolder + +class InputMappingControlSettingViewHolder( + private val binding: ListItemMappingBinding, + adapter: SettingsAdapter +) : SettingViewHolder(binding.getRoot(), adapter) { + lateinit var setting: InputMappingControlSetting + + override val item: SettingsItem + get() = setting + + override fun bind(item: SettingsItem) { + setting = item as InputMappingControlSetting + + binding.textSettingName.text = setting.name + binding.textSettingDescription.text = setting.value + binding.buttonAdvancedSettings.setOnClickListener { clicked: View -> onLongClick(clicked) } + + setStyle(binding.textSettingName, setting) + } + + override fun onClick(clicked: View) { + if (!setting.isEditable) { + showNotRuntimeEditableError() + return + } + + if (setting.isInput) + adapter.onInputMappingClick(setting, bindingAdapterPosition) + else + adapter.onAdvancedInputMappingClick(setting, bindingAdapterPosition) + + setStyle(binding.textSettingName, setting) + } + + override fun onLongClick(clicked: View): Boolean { + if (!setting.isEditable) { + showNotRuntimeEditableError() + return true + } + + adapter.onAdvancedInputMappingClick(setting, bindingAdapterPosition) + + return true + } +} From a56ee1a62e0781df7eefa4a0408a3d84d5a2b807 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 14:18:27 -0400 Subject: [PATCH 051/120] Android: Convert GameFile to Kotlin --- .../dolphinemu/adapters/GameAdapter.kt | 8 +- .../dolphinemu/adapters/GameRowPresenter.kt | 6 +- .../dolphinemu/dialogs/GameDetailsDialog.kt | 50 ++++++------ .../dolphinemu/fragments/ConvertFragment.kt | 22 ++++-- .../dolphinemu/dolphinemu/model/GameFile.java | 78 ------------------- .../dolphinemu/dolphinemu/model/GameFile.kt | 69 ++++++++++++++++ .../dolphinemu/utils/CoverHelper.kt | 6 +- Source/Android/jni/AndroidCommon/IDCache.cpp | 2 +- 8 files changed, 120 insertions(+), 121 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt index 103ba4a318..520c96d812 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt @@ -61,12 +61,12 @@ class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapte holder.apply { if (BooleanSetting.MAIN_SHOW_GAME_TITLES.boolean) { - binding.textGameTitle.text = gameFile.title + binding.textGameTitle.text = gameFile.getTitle() binding.textGameTitle.visibility = View.VISIBLE binding.textGameTitleInner.visibility = View.GONE binding.textGameCaption.visibility = View.VISIBLE } else { - binding.textGameTitleInner.text = gameFile.title + binding.textGameTitleInner.text = gameFile.getTitle() binding.textGameTitleInner.visibility = View.VISIBLE binding.textGameTitle.visibility = View.GONE binding.textGameCaption.visibility = View.GONE @@ -94,9 +94,9 @@ class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapte holder.apply { if (GameFileCacheManager.findSecondDisc(gameFile) != null) { binding.textGameCaption.text = - context.getString(R.string.disc_number, gameFile.discNumber + 1) + context.getString(R.string.disc_number, gameFile.getDiscNumber() + 1) } else { - binding.textGameCaption.text = gameFile.company + binding.textGameCaption.text = gameFile.getCompany() } holder.gameFile = gameFile binding.root.onFocusChangeListener = diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt index c23b4b3e14..b355dc8f6d 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt @@ -48,7 +48,7 @@ class GameRowPresenter(private val mActivity: FragmentActivity) : Presenter() { holder.apply { imageScreenshot.setImageDrawable(null) - cardParent.titleText = gameFile.title + cardParent.titleText = gameFile.getTitle() holder.gameFile = gameFile // Set the background color of the card @@ -64,9 +64,9 @@ class GameRowPresenter(private val mActivity: FragmentActivity) : Presenter() { if (GameFileCacheManager.findSecondDisc(gameFile) != null) { holder.cardParent.contentText = - context.getString(R.string.disc_number, gameFile.discNumber + 1) + context.getString(R.string.disc_number, gameFile.getDiscNumber() + 1) } else { - holder.cardParent.contentText = gameFile.company + holder.cardParent.contentText = gameFile.getCompany() } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt index 83d69ebbb4..53b1467bbc 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/dialogs/GameDetailsDialog.kt @@ -25,8 +25,8 @@ class GameDetailsDialog : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val gameFile = GameFileCacheManager.addOrGet(requireArguments().getString(ARG_GAME_PATH)) - val country = resources.getStringArray(R.array.countryNames)[gameFile.country] - val fileSize = NativeLibrary.FormatSize(gameFile.fileSize, 2) + val country = resources.getStringArray(R.array.countryNames)[gameFile.getCountry()] + val fileSize = NativeLibrary.FormatSize(gameFile.getFileSize(), 2) // TODO: Remove dialog_game_details_tv if we switch to an AppCompatActivity for leanback val binding: DialogGameDetailsBinding @@ -35,16 +35,16 @@ class GameDetailsDialog : DialogFragment() { if (requireActivity() is AppCompatActivity) { binding = DialogGameDetailsBinding.inflate(layoutInflater) binding.apply { - textGameTitle.text = gameFile.title - textDescription.text = gameFile.description - if (gameFile.description.isEmpty()) { + textGameTitle.text = gameFile.getTitle() + textDescription.text = gameFile.getDescription() + if (gameFile.getDescription().isEmpty()) { textDescription.visibility = View.GONE } textCountry.text = country - textCompany.text = gameFile.company - textGameId.text = gameFile.gameId - textRevision.text = gameFile.revision.toString() + textCompany.text = gameFile.getCompany() + textGameId.text = gameFile.getGameId() + textRevision.text = gameFile.getRevision().toString() if (!gameFile.shouldShowFileFormatDetails()) { labelFileFormat.setText(R.string.game_details_file_size) @@ -55,19 +55,19 @@ class GameDetailsDialog : DialogFragment() { labelBlockSize.visibility = View.GONE textBlockSize.visibility = View.GONE } else { - val blockSize = gameFile.blockSize - val compression = gameFile.compressionMethod + val blockSize = gameFile.getBlockSize() + val compression = gameFile.getCompressionMethod() textFileFormat.text = resources.getString( R.string.game_details_size_and_format, - gameFile.fileFormatName, + gameFile.getFileFormatName(), fileSize ) if (compression.isEmpty()) { textCompression.setText(R.string.game_details_no_compression) } else { - textCompression.text = gameFile.compressionMethod + textCompression.text = gameFile.getCompressionMethod() } if (blockSize > 0) { @@ -87,16 +87,16 @@ class GameDetailsDialog : DialogFragment() { } else { tvBinding = DialogGameDetailsTvBinding.inflate(layoutInflater) tvBinding.apply { - textGameTitle.text = gameFile.title - textDescription.text = gameFile.description - if (gameFile.description.isEmpty()) { + textGameTitle.text = gameFile.getTitle() + textDescription.text = gameFile.getDescription() + if (gameFile.getDescription().isEmpty()) { tvBinding.textDescription.visibility = View.GONE } textCountry.text = country - textCompany.text = gameFile.company - textGameId.text = gameFile.gameId - textRevision.text = gameFile.revision.toString() + textCompany.text = gameFile.getCompany() + textGameId.text = gameFile.getGameId() + textRevision.text = gameFile.getRevision().toString() if (!gameFile.shouldShowFileFormatDetails()) { labelFileFormat.setText(R.string.game_details_file_size) @@ -107,19 +107,19 @@ class GameDetailsDialog : DialogFragment() { labelBlockSize.visibility = View.GONE textBlockSize.visibility = View.GONE } else { - val blockSize = gameFile.blockSize - val compression = gameFile.compressionMethod + val blockSize = gameFile.getBlockSize() + val compression = gameFile.getCompressionMethod() textFileFormat.text = resources.getString( R.string.game_details_size_and_format, - gameFile.fileFormatName, + gameFile.getFileFormatName(), fileSize ) if (compression.isEmpty()) { textCompression.setText(R.string.game_details_no_compression) } else { - textCompression.text = gameFile.compressionMethod + textCompression.text = gameFile.getCompressionMethod() } if (blockSize > 0) { @@ -141,9 +141,9 @@ class GameDetailsDialog : DialogFragment() { } private suspend fun loadGameBanner(imageView: ImageView, gameFile: GameFile) { - val vector = gameFile.banner - val width = gameFile.bannerWidth - val height = gameFile.bannerHeight + val vector = gameFile.getBanner() + val width = gameFile.getBannerWidth() + val height = gameFile.getBannerHeight() imageView.scaleType = ImageView.ScaleType.FIT_CENTER val request = ImageRequest.Builder(imageView.context) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt index 01a5d97743..0f5760a210 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt @@ -210,7 +210,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { R.array.convertFormatValues, format ) - if (gameFile.blobType == BLOB_TYPE_ISO) { + if (gameFile.getBlobType() == BLOB_TYPE_ISO) { setDropdownSelection( binding.dropdownFormat, format, @@ -240,6 +240,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownBlockSize.adapter.getItem(0).toString(), false ) } + BLOB_TYPE_WIA -> { populateDropdown( binding.blockSize, @@ -253,6 +254,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownBlockSize.adapter.getItem(0).toString(), false ) } + BLOB_TYPE_RVZ -> { populateDropdown( binding.blockSize, @@ -266,6 +268,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownBlockSize.adapter.getItem(2).toString(), false ) } + else -> clearDropdown(binding.blockSize, binding.dropdownBlockSize, blockSize) } } @@ -285,6 +288,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownCompression.adapter.getItem(0).toString(), false ) } + BLOB_TYPE_WIA -> { populateDropdown( binding.compression, @@ -298,6 +302,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownCompression.adapter.getItem(0).toString(), false ) } + BLOB_TYPE_RVZ -> { populateDropdown( binding.compression, @@ -311,6 +316,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { binding.dropdownCompression.adapter.getItem(4).toString(), false ) } + else -> clearDropdown( binding.compression, binding.dropdownCompression, @@ -336,6 +342,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { ).toString(), false ) } + COMPRESSION_ZSTD -> { // TODO: Query DiscIO for the supported compression levels, like we do in DolphinQt? populateDropdown( @@ -352,6 +359,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { ).toString(), false ) } + else -> clearDropdown( binding.compressionLevel, binding.dropdownCompressionLevel, @@ -362,7 +370,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { private fun populateRemoveJunkData() { val scrubbingAllowed = format.getValue(requireContext()) != BLOB_TYPE_RVZ && - !gameFile.isDatelDisc + !gameFile.isDatelDisc() binding.switchRemoveJunkData.isEnabled = scrubbingAllowed if (!scrubbingAllowed) binding.switchRemoveJunkData.isChecked = false @@ -374,11 +382,11 @@ class ConvertFragment : Fragment(), View.OnClickListener { var action = Runnable { showSavePrompt() } - if (gameFile.isNKit) { + if (gameFile.isNKit()) { action = addAreYouSureDialog(action, R.string.convert_warning_nkit) } - if (!scrub && format == BLOB_TYPE_GCZ && !gameFile.isDatelDisc && gameFile.platform == Platform.WII.toInt()) { + if (!scrub && format == BLOB_TYPE_GCZ && !gameFile.isDatelDisc() && gameFile.getPlatform() == Platform.WII.toInt()) { action = addAreYouSureDialog(action, R.string.convert_warning_gcz) } @@ -401,7 +409,7 @@ class ConvertFragment : Fragment(), View.OnClickListener { } private fun showSavePrompt() { - val originalPath = gameFile.path + val originalPath = gameFile.getPath() val filename = StringBuilder(File(originalPath).name) val dotIndex = filename.lastIndexOf(".") @@ -449,9 +457,9 @@ class ConvertFragment : Fragment(), View.OnClickListener { thread = Thread { val success = NativeLibrary.ConvertDiscImage( - gameFile.path, + gameFile.getPath(), outPath, - gameFile.platform, + gameFile.getPlatform(), format.getValue(context), blockSize.getValueOr(context, 0), compression.getValueOr(context, 0), diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java deleted file mode 100644 index 26f4c169ce..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.java +++ /dev/null @@ -1,78 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.model; - -import androidx.annotation.Keep; - -public class GameFile -{ - public static int REGION_NTSC_J = 0; - public static int REGION_NTSC_U = 1; - public static int REGION_PAL = 2; - public static int REGION_NTSC_K = 4; - - @Keep - private long mPointer; - - @Keep - private GameFile(long pointer) - { - mPointer = pointer; - } - - public native static GameFile parse(String path); - - @Override - public native void finalize(); - - public native int getPlatform(); - - public native String getTitle(); - - public native String getDescription(); - - public native String getCompany(); - - public native int getCountry(); - - public native int getRegion(); - - public native String getPath(); - - public native String getGameId(); - - public native String getGameTdbId(); - - public native int getDiscNumber(); - - public native int getRevision(); - - public native int getBlobType(); - - public native String getFileFormatName(); - - public native long getBlockSize(); - - public native String getCompressionMethod(); - - public native boolean shouldShowFileFormatDetails(); - - public native boolean shouldAllowConversion(); - - public native long getFileSize(); - - public native boolean isDatelDisc(); - - public native boolean isNKit(); - - public native int[] getBanner(); - - public native int getBannerWidth(); - - public native int getBannerHeight(); - - public String getCustomCoverPath() - { - return getPath().substring(0, getPath().lastIndexOf(".")) + ".cover.png"; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt new file mode 100644 index 0000000000..347433f552 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFile.kt @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.model + +import androidx.annotation.Keep + +@Keep +class GameFile private constructor(private val pointer: Long) { + external fun finalize() + + external fun getPlatform(): Int + + external fun getTitle(): String + + external fun getDescription(): String + + external fun getCompany(): String + + external fun getCountry(): Int + + external fun getRegion(): Int + + external fun getPath(): String + + external fun getGameId(): String + + external fun getGameTdbId(): String + + external fun getDiscNumber(): Int + + external fun getRevision(): Int + + external fun getBlobType(): Int + + external fun getFileFormatName(): String + + external fun getBlockSize(): Long + + external fun getCompressionMethod(): String + + external fun shouldShowFileFormatDetails(): Boolean + + external fun shouldAllowConversion(): Boolean + + external fun getFileSize(): Long + + external fun isDatelDisc(): Boolean + + external fun isNKit(): Boolean + + external fun getBanner(): IntArray + + external fun getBannerWidth(): Int + + external fun getBannerHeight(): Int + + val customCoverPath: String + get() = "${getPath().substring(0, getPath().lastIndexOf("."))}.cover.png" + + companion object { + var REGION_NTSC_J = 0 + var REGION_NTSC_U = 1 + var REGION_PAL = 2 + var REGION_NTSC_K = 4 + + @JvmStatic + external fun parse(path: String): GameFile? + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt index 9359d38eb3..172be697e9 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoverHelper.kt @@ -8,16 +8,16 @@ object CoverHelper { @JvmStatic fun buildGameTDBUrl(game: GameFile, region: String?): String { val baseUrl = "https://art.gametdb.com/wii/cover/%s/%s.png" - return String.format(baseUrl, region, game.gameTdbId) + return String.format(baseUrl, region, game.getGameTdbId()) } @JvmStatic fun getRegion(game: GameFile): String { - val region: String = when (game.region) { + val region: String = when (game.getRegion()) { GameFile.REGION_NTSC_J -> "JA" GameFile.REGION_NTSC_U -> "US" GameFile.REGION_NTSC_K -> "KO" - GameFile.REGION_PAL -> when (game.country) { + GameFile.REGION_PAL -> when (game.getCountry()) { 3 -> "AU" // Australia 4 -> "FR" // France 5 -> "DE" // Germany diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index e4cacaa462..2396f0960b 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -563,7 +563,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) const jclass game_file_class = env->FindClass("org/dolphinemu/dolphinemu/model/GameFile"); s_game_file_class = reinterpret_cast(env->NewGlobalRef(game_file_class)); - s_game_file_pointer = env->GetFieldID(game_file_class, "mPointer", "J"); + s_game_file_pointer = env->GetFieldID(game_file_class, "pointer", "J"); s_game_file_constructor = env->GetMethodID(game_file_class, "", "(J)V"); env->DeleteLocalRef(game_file_class); From 09c2c6541d5d4d1aefa42c4bf1616bcceeb37f1d Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 14:19:28 -0400 Subject: [PATCH 052/120] Android: Convert GameFileCache to Kotlin --- .../dolphinemu/model/GameFileCache.java | 139 ---------------- .../dolphinemu/model/GameFileCache.kt | 152 ++++++++++++++++++ .../dolphinemu/ui/main/MainPresenter.kt | 2 +- Source/Android/jni/AndroidCommon/IDCache.cpp | 2 +- 4 files changed, 154 insertions(+), 141 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java deleted file mode 100644 index 59c8893dcf..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.java +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.model; - -import androidx.annotation.Keep; - -import org.dolphinemu.dolphinemu.NativeLibrary; -import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting; -import org.dolphinemu.dolphinemu.features.settings.model.Settings; -import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile; -import org.dolphinemu.dolphinemu.utils.ContentHandler; -import org.dolphinemu.dolphinemu.utils.IniFile; - -import java.io.File; -import java.util.LinkedHashSet; - -public class GameFileCache -{ - @Keep - private long mPointer; - - public GameFileCache() - { - mPointer = newGameFileCache(); - } - - private static native long newGameFileCache(); - - @Override - public native void finalize(); - - public static void addGameFolder(String path) - { - File dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN); - IniFile dolphinIni = new IniFile(dolphinFile); - LinkedHashSet pathSet = getPathSet(false); - int totalISOPaths = - dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0); - - if (!pathSet.contains(path)) - { - dolphinIni.setInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, - totalISOPaths + 1); - dolphinIni.setString(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATH_BASE + - totalISOPaths, path); - dolphinIni.save(dolphinFile); - NativeLibrary.ReloadConfig(); - } - } - - private static LinkedHashSet getPathSet(boolean removeNonExistentFolders) - { - File dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN); - IniFile dolphinIni = new IniFile(dolphinFile); - LinkedHashSet pathSet = new LinkedHashSet<>(); - int totalISOPaths = - dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0); - - for (int i = 0; i < totalISOPaths; i++) - { - String path = dolphinIni.getString(Settings.SECTION_INI_GENERAL, - SettingsFile.KEY_ISO_PATH_BASE + i, ""); - - if (ContentHandler.isContentUri(path) ? ContentHandler.exists(path) : new File(path).exists()) - { - pathSet.add(path); - } - } - - if (removeNonExistentFolders && totalISOPaths > pathSet.size()) - { - int setIndex = 0; - - dolphinIni.setInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, - pathSet.size()); - - // One or more folders have been removed. - for (String entry : pathSet) - { - dolphinIni.setString(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATH_BASE + - setIndex, entry); - - setIndex++; - } - - // Delete known unnecessary keys. Ignore i values beyond totalISOPaths. - for (int i = setIndex; i < totalISOPaths; i++) - { - dolphinIni.deleteKey(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATH_BASE + i); - } - - dolphinIni.save(dolphinFile); - NativeLibrary.ReloadConfig(); - } - - return pathSet; - } - - public static String[] getAllGamePaths() - { - boolean recursiveScan = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.getBoolean(); - - LinkedHashSet folderPathsSet = getPathSet(true); - - String[] folderPaths = folderPathsSet.toArray(new String[0]); - - return getAllGamePaths(folderPaths, recursiveScan); - } - - public static native String[] getAllGamePaths(String[] folderPaths, boolean recursiveScan); - - public synchronized native int getSize(); - - public synchronized native GameFile[] getAllGames(); - - public synchronized native GameFile addOrGet(String gamePath); - - /** - * Sets the list of games to cache. - * - * Games which are in the passed-in list but not in the cache are scanned and added to the cache, - * and games which are in the cache but not in the passed-in list are removed from the cache. - * - * @return true if the cache was modified - */ - public synchronized native boolean update(String[] gamePaths); - - /** - * For each game that already is in the cache, scans the folder that contains the game - * for additional metadata files (PNG/XML). - * - * @return true if the cache was modified - */ - public synchronized native boolean updateAdditionalMetadata(); - - public synchronized native boolean load(); - - public synchronized native boolean save(); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt new file mode 100644 index 0000000000..74b1bd535a --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.model + +import androidx.annotation.Keep +import org.dolphinemu.dolphinemu.NativeLibrary +import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting +import org.dolphinemu.dolphinemu.features.settings.model.Settings +import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile +import org.dolphinemu.dolphinemu.utils.ContentHandler +import org.dolphinemu.dolphinemu.utils.IniFile +import java.io.File + +class GameFileCache { + @Keep + private val pointer: Long = newGameFileCache() + + external fun finalize() + + @Synchronized + external fun getSize(): Int + + @Synchronized + external fun getAllGames(): Array + + @Synchronized + external fun addOrGet(gamePath: String): GameFile? + + /** + * Sets the list of games to cache. + * + * Games which are in the passed-in list but not in the cache are scanned and added to the cache, + * and games which are in the cache but not in the passed-in list are removed from the cache. + * + * @return true if the cache was modified + */ + @Synchronized + external fun update(gamePaths: Array): Boolean + + /** + * For each game that already is in the cache, scans the folder that contains the game + * for additional metadata files (PNG/XML). + * + * @return true if the cache was modified + */ + @Synchronized + external fun updateAdditionalMetadata(): Boolean + + @Synchronized + external fun load(): Boolean + + @Synchronized + external fun save(): Boolean + + companion object { + @JvmStatic + private external fun newGameFileCache(): Long + + fun addGameFolder(path: String) { + val dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN) + val dolphinIni = IniFile(dolphinFile) + val pathSet = getPathSet(false) + val totalISOPaths = + dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0) + + if (!pathSet.contains(path)) { + dolphinIni.setInt( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATHS, + totalISOPaths + 1 + ) + dolphinIni.setString( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATH_BASE + totalISOPaths, + path + ) + dolphinIni.save(dolphinFile) + NativeLibrary.ReloadConfig() + } + } + + private fun getPathSet(removeNonExistentFolders: Boolean): LinkedHashSet { + val dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN) + val dolphinIni = IniFile(dolphinFile) + val pathSet = LinkedHashSet() + val totalISOPaths = + dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0) + + for (i in 0 until totalISOPaths) { + val path = dolphinIni.getString( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATH_BASE + i, + "" + ) + + val pathExists = if (ContentHandler.isContentUri(path)) + ContentHandler.exists(path) + else + File(path).exists() + if (pathExists) { + pathSet.add(path) + } + } + + if (removeNonExistentFolders && totalISOPaths > pathSet.size) { + var setIndex = 0 + + dolphinIni.setInt( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATHS, + pathSet.size + ) + + // One or more folders have been removed. + for (entry in pathSet) { + dolphinIni.setString( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATH_BASE + setIndex, + entry + ) + setIndex++ + } + + // Delete known unnecessary keys. Ignore i values beyond totalISOPaths. + for (i in setIndex until totalISOPaths) { + dolphinIni.deleteKey( + Settings.SECTION_INI_GENERAL, + SettingsFile.KEY_ISO_PATH_BASE + i + ) + } + + dolphinIni.save(dolphinFile) + NativeLibrary.ReloadConfig() + } + return pathSet + } + + @JvmStatic + fun getAllGamePaths(): Array { + val recursiveScan = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.boolean + val folderPathsSet = getPathSet(true) + val folderPaths = folderPathsSet.toTypedArray() + return getAllGamePaths(folderPaths, recursiveScan) + } + + @JvmStatic + external fun getAllGamePaths( + folderPaths: Array, + recursiveScan: Boolean + ): Array + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt index 64695a0ace..e77b694096 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/MainPresenter.kt @@ -123,7 +123,7 @@ class MainPresenter(private val mainView: MainView, private val activity: Fragme fun onResume() { if (dirToAdd != null) { - GameFileCache.addGameFolder(dirToAdd) + GameFileCache.addGameFolder(dirToAdd!!) dirToAdd = null } diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 2396f0960b..f83707cebe 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -570,7 +570,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) const jclass game_file_cache_class = env->FindClass("org/dolphinemu/dolphinemu/model/GameFileCache"); s_game_file_cache_class = reinterpret_cast(env->NewGlobalRef(game_file_cache_class)); - s_game_file_cache_pointer = env->GetFieldID(game_file_cache_class, "mPointer", "J"); + s_game_file_cache_pointer = env->GetFieldID(game_file_cache_class, "pointer", "J"); env->DeleteLocalRef(game_file_cache_class); const jclass analytics_class = env->FindClass("org/dolphinemu/dolphinemu/utils/Analytics"); From ed9467dc1bcfea079b4c7c9a6df725e1616bdeb3 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 14:19:41 -0400 Subject: [PATCH 053/120] Android: Convert HomeScreenChannel to Kotlin --- .../dolphinemu/model/HomeScreenChannel.java | 63 ------------------- .../dolphinemu/model/HomeScreenChannel.kt | 12 ++++ 2 files changed, 12 insertions(+), 63 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.java deleted file mode 100644 index 6e35b1de83..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.java +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.model; - -import android.net.Uri; - -/** - * Represents a home screen channel for Android TV api 26+ - */ -public class HomeScreenChannel -{ - private long channelId; - private String name; - private String description; - private Uri appLinkIntentUri; - - public HomeScreenChannel(String name, String description, Uri appLinkIntentUri) - { - this.name = name; - this.description = description; - this.appLinkIntentUri = appLinkIntentUri; - } - - public long getChannelId() - { - return channelId; - } - - public void setChannelId(long channelId) - { - this.channelId = channelId; - } - - public String getName() - { - return name; - } - - public void setName(String name) - { - this.name = name; - } - - public String getDescription() - { - return description; - } - - public void setDescription(String description) - { - this.description = description; - } - - public Uri getAppLinkIntentUri() - { - return appLinkIntentUri; - } - - public void setAppLinkIntentUri(Uri appLinkIntentUri) - { - this.appLinkIntentUri = appLinkIntentUri; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.kt new file mode 100644 index 0000000000..b5b269fa77 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/HomeScreenChannel.kt @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.model + +import android.net.Uri + +/** + * Represents a home screen channel for Android TV api 26+ + */ +class HomeScreenChannel(var name: String, var description: String, var appLinkIntentUri: Uri) { + var channelId: Long = 0 +} From 6a19629fc6da96fc8fb7f65bae9c72ba4a08f7b0 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Sat, 10 Jun 2023 14:19:52 -0400 Subject: [PATCH 054/120] Android: Convert TvSettingsItem to Kotlin --- .../dolphinemu/model/TvSettingsItem.java | 32 ------------------- .../dolphinemu/model/TvSettingsItem.kt | 5 +++ 2 files changed, 5 insertions(+), 32 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.kt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java deleted file mode 100644 index c9c4019d94..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.java +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.model; - -public final class TvSettingsItem -{ - private final int mItemId; - private final int mIconId; - private final int mLabelId; - - public TvSettingsItem(int itemId, int iconId, int labelId) - { - mItemId = itemId; - mIconId = iconId; - mLabelId = labelId; - } - - public int getItemId() - { - return mItemId; - } - - public int getIconId() - { - return mIconId; - } - - public int getLabelId() - { - return mLabelId; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.kt new file mode 100644 index 0000000000..5c4d5aa3c9 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/TvSettingsItem.kt @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.model + +class TvSettingsItem(val itemId: Int, val iconId: Int, val labelId: Int) From 28e8117b90063b211ad47171bb64ff9284eec652 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 10 Apr 2022 11:06:40 +0200 Subject: [PATCH 055/120] Jit: Automatically clear cache when JIT settings are updated This fixes a problem where changing the JIT debug settings on Android while a game was running wouldn't cause the changed settings to apply to code blocks that already had been compiled. --- .../CachedInterpreter/CachedInterpreter.cpp | 5 +- Source/Core/Core/PowerPC/Jit64/Jit.cpp | 5 +- Source/Core/Core/PowerPC/JitArm64/Jit.cpp | 5 +- .../Core/Core/PowerPC/JitCommon/JitBase.cpp | 85 +++++++++++-------- Source/Core/Core/PowerPC/JitCommon/JitBase.h | 9 +- Source/Core/DolphinQt/MenuBar.cpp | 48 ++++------- 6 files changed, 83 insertions(+), 74 deletions(-) diff --git a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp index eca1afd32d..8fbed42f06 100644 --- a/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp +++ b/Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreter.cpp @@ -82,12 +82,13 @@ CachedInterpreter::~CachedInterpreter() = default; void CachedInterpreter::Init() { + RefreshConfig(); + m_code.reserve(CODE_SIZE / sizeof(Instruction)); jo.enableBlocklink = false; m_block_cache.Init(); - UpdateMemoryAndExceptionOptions(); code_block.m_stats = &js.st; code_block.m_gpa = &js.gpa; @@ -383,5 +384,5 @@ void CachedInterpreter::ClearCache() { m_code.clear(); m_block_cache.Clear(); - UpdateMemoryAndExceptionOptions(); + RefreshConfig(); } diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.cpp b/Source/Core/Core/PowerPC/Jit64/Jit.cpp index fcd9d1dc3d..e5aa885263 100644 --- a/Source/Core/Core/PowerPC/Jit64/Jit.cpp +++ b/Source/Core/Core/PowerPC/Jit64/Jit.cpp @@ -251,6 +251,8 @@ bool Jit64::BackPatch(SContext* ctx) void Jit64::Init() { + RefreshConfig(); + EnableBlockLink(); auto& memory = m_system.GetMemory(); @@ -258,7 +260,6 @@ void Jit64::Init() jo.fastmem_arena = m_fastmem_enabled && memory.InitFastmemArena(); jo.optimizeGatherPipe = true; jo.accurateSinglePrecision = true; - UpdateMemoryAndExceptionOptions(); js.fastmemLoadStore = nullptr; js.compilerPC = 0; @@ -306,7 +307,7 @@ void Jit64::ClearCache() m_const_pool.Clear(); ClearCodeSpace(); Clear(); - UpdateMemoryAndExceptionOptions(); + RefreshConfig(); ResetFreeMemoryRanges(); } diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp index 664f8a6525..99d5da4b6f 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp @@ -47,6 +47,8 @@ JitArm64::~JitArm64() = default; void JitArm64::Init() { + RefreshConfig(); + const size_t child_code_size = m_mmu_enabled ? FARCODE_SIZE_MMU : FARCODE_SIZE; AllocCodeSpace(CODE_SIZE + child_code_size); AddChildCodeSpace(&m_far_code, child_code_size); @@ -55,7 +57,6 @@ void JitArm64::Init() jo.fastmem_arena = m_fastmem_enabled && memory.InitFastmemArena(); jo.optimizeGatherPipe = true; - UpdateMemoryAndExceptionOptions(); SetBlockLinkingEnabled(true); SetOptimizationEnabled(true); gpr.Init(this); @@ -157,7 +158,7 @@ void JitArm64::ClearCache() const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes; ClearCodeSpace(); m_far_code.ClearCodeSpace(); - UpdateMemoryAndExceptionOptions(); + RefreshConfig(); GenerateAsm(); diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp index 17d9fe1bea..58d61499dc 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp @@ -3,6 +3,10 @@ #include "Core/PowerPC/JitCommon/JitBase.h" +#include +#include +#include + #include "Common/Align.h" #include "Common/CommonTypes.h" #include "Common/MemoryUtil.h" @@ -52,6 +56,31 @@ // After resetting the stack to the top, we call _resetstkoflw() to restore // the guard page at the 256kb mark. +const std::array*>, 22> JitBase::JIT_SETTINGS{{ + {&JitBase::bJITOff, &Config::MAIN_DEBUG_JIT_OFF}, + {&JitBase::bJITLoadStoreOff, &Config::MAIN_DEBUG_JIT_LOAD_STORE_OFF}, + {&JitBase::bJITLoadStorelXzOff, &Config::MAIN_DEBUG_JIT_LOAD_STORE_LXZ_OFF}, + {&JitBase::bJITLoadStorelwzOff, &Config::MAIN_DEBUG_JIT_LOAD_STORE_LWZ_OFF}, + {&JitBase::bJITLoadStorelbzxOff, &Config::MAIN_DEBUG_JIT_LOAD_STORE_LBZX_OFF}, + {&JitBase::bJITLoadStoreFloatingOff, &Config::MAIN_DEBUG_JIT_LOAD_STORE_FLOATING_OFF}, + {&JitBase::bJITLoadStorePairedOff, &Config::MAIN_DEBUG_JIT_LOAD_STORE_PAIRED_OFF}, + {&JitBase::bJITFloatingPointOff, &Config::MAIN_DEBUG_JIT_FLOATING_POINT_OFF}, + {&JitBase::bJITIntegerOff, &Config::MAIN_DEBUG_JIT_INTEGER_OFF}, + {&JitBase::bJITPairedOff, &Config::MAIN_DEBUG_JIT_PAIRED_OFF}, + {&JitBase::bJITSystemRegistersOff, &Config::MAIN_DEBUG_JIT_SYSTEM_REGISTERS_OFF}, + {&JitBase::bJITBranchOff, &Config::MAIN_DEBUG_JIT_BRANCH_OFF}, + {&JitBase::bJITRegisterCacheOff, &Config::MAIN_DEBUG_JIT_REGISTER_CACHE_OFF}, + {&JitBase::m_enable_debugging, &Config::MAIN_ENABLE_DEBUGGING}, + {&JitBase::m_enable_branch_following, &Config::MAIN_JIT_FOLLOW_BRANCH}, + {&JitBase::m_enable_float_exceptions, &Config::MAIN_FLOAT_EXCEPTIONS}, + {&JitBase::m_enable_div_by_zero_exceptions, &Config::MAIN_DIVIDE_BY_ZERO_EXCEPTIONS}, + {&JitBase::m_low_dcbz_hack, &Config::MAIN_LOW_DCBZ_HACK}, + {&JitBase::m_fprf, &Config::MAIN_FPRF}, + {&JitBase::m_accurate_nans, &Config::MAIN_ACCURATE_NANS}, + {&JitBase::m_fastmem_enabled, &Config::MAIN_FASTMEM}, + {&JitBase::m_accurate_cpu_cache_enabled, &Config::MAIN_ACCURATE_CPU_CACHE}, +}}; + const u8* JitBase::Dispatch(JitBase& jit) { return jit.GetBlockCache()->Dispatch(); @@ -66,9 +95,11 @@ JitBase::JitBase(Core::System& system) : m_code_buffer(code_buffer_size), m_system(system), m_ppc_state(system.GetPPCState()), m_mmu(system.GetMMU()) { - m_registered_config_callback_id = - CPUThreadConfigCallback::AddConfigChangedCallback([this] { RefreshConfig(); }); - RefreshConfig(); + m_registered_config_callback_id = CPUThreadConfigCallback::AddConfigChangedCallback([this] { + if (DoesConfigNeedRefresh()) + ClearCache(); + }); + // The JIT is responsible for calling RefreshConfig on Init and ClearCache } JitBase::~JitBase() @@ -76,31 +107,20 @@ JitBase::~JitBase() CPUThreadConfigCallback::RemoveConfigChangedCallback(m_registered_config_callback_id); } +bool JitBase::DoesConfigNeedRefresh() +{ + return std::any_of(JIT_SETTINGS.begin(), JIT_SETTINGS.end(), [this](const auto& pair) { + return this->*pair.first != Config::Get(*pair.second); + }); +} + void JitBase::RefreshConfig() { - bJITOff = Config::Get(Config::MAIN_DEBUG_JIT_OFF); - bJITLoadStoreOff = Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_OFF); - bJITLoadStorelXzOff = Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_LXZ_OFF); - bJITLoadStorelwzOff = Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_LWZ_OFF); - bJITLoadStorelbzxOff = Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_LBZX_OFF); - bJITLoadStoreFloatingOff = Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_FLOATING_OFF); - bJITLoadStorePairedOff = Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_PAIRED_OFF); - bJITFloatingPointOff = Config::Get(Config::MAIN_DEBUG_JIT_FLOATING_POINT_OFF); - bJITIntegerOff = Config::Get(Config::MAIN_DEBUG_JIT_INTEGER_OFF); - bJITPairedOff = Config::Get(Config::MAIN_DEBUG_JIT_PAIRED_OFF); - bJITSystemRegistersOff = Config::Get(Config::MAIN_DEBUG_JIT_SYSTEM_REGISTERS_OFF); - bJITBranchOff = Config::Get(Config::MAIN_DEBUG_JIT_BRANCH_OFF); - bJITRegisterCacheOff = Config::Get(Config::MAIN_DEBUG_JIT_REGISTER_CACHE_OFF); - m_enable_debugging = Config::Get(Config::MAIN_ENABLE_DEBUGGING); - m_enable_float_exceptions = Config::Get(Config::MAIN_FLOAT_EXCEPTIONS); - m_enable_div_by_zero_exceptions = Config::Get(Config::MAIN_DIVIDE_BY_ZERO_EXCEPTIONS); - m_low_dcbz_hack = Config::Get(Config::MAIN_LOW_DCBZ_HACK); - m_fprf = Config::Get(Config::MAIN_FPRF); - m_accurate_nans = Config::Get(Config::MAIN_ACCURATE_NANS); - m_fastmem_enabled = Config::Get(Config::MAIN_FASTMEM); + for (const auto& [member, config_info] : JIT_SETTINGS) + this->*member = Config::Get(*config_info); + m_mmu_enabled = m_system.IsMMUMode(); m_pause_on_panic_enabled = m_system.IsPauseOnPanicMode(); - m_accurate_cpu_cache_enabled = Config::Get(Config::MAIN_ACCURATE_CPU_CACHE); if (m_accurate_cpu_cache_enabled) { m_fastmem_enabled = false; @@ -109,9 +129,15 @@ void JitBase::RefreshConfig() } analyzer.SetDebuggingEnabled(m_enable_debugging); - analyzer.SetBranchFollowingEnabled(Config::Get(Config::MAIN_JIT_FOLLOW_BRANCH)); + analyzer.SetBranchFollowingEnabled(m_enable_branch_following); analyzer.SetFloatExceptionsEnabled(m_enable_float_exceptions); analyzer.SetDivByZeroExceptionsEnabled(m_enable_div_by_zero_exceptions); + + bool any_watchpoints = m_system.GetPowerPC().GetMemChecks().HasAny(); + jo.fastmem = m_fastmem_enabled && jo.fastmem_arena && (m_ppc_state.msr.DR || !any_watchpoints); + jo.memcheck = m_mmu_enabled || m_pause_on_panic_enabled || any_watchpoints; + jo.fp_exceptions = m_enable_float_exceptions; + jo.div_by_zero_exceptions = m_enable_div_by_zero_exceptions; } void JitBase::InitBLROptimization() @@ -233,15 +259,6 @@ bool JitBase::CanMergeNextInstructions(int count) const return true; } -void JitBase::UpdateMemoryAndExceptionOptions() -{ - bool any_watchpoints = m_system.GetPowerPC().GetMemChecks().HasAny(); - jo.fastmem = m_fastmem_enabled && jo.fastmem_arena && (m_ppc_state.msr.DR || !any_watchpoints); - jo.memcheck = m_mmu_enabled || m_pause_on_panic_enabled || any_watchpoints; - jo.fp_exceptions = m_enable_float_exceptions; - jo.div_by_zero_exceptions = m_enable_div_by_zero_exceptions; -} - bool JitBase::ShouldHandleFPExceptionForInstruction(const PPCAnalyst::CodeOp* op) { if (jo.fp_exceptions) diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.h b/Source/Core/Core/PowerPC/JitCommon/JitBase.h index c2e41f8e52..2846938cf4 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.h @@ -3,12 +3,15 @@ #pragma once +#include #include #include #include +#include #include "Common/BitSet.h" #include "Common/CommonTypes.h" +#include "Common/Config/ConfigInfo.h" #include "Common/x64Emitter.h" #include "Core/CPUThreadConfigCallback.h" #include "Core/ConfigManager.h" @@ -145,6 +148,7 @@ protected: bool bJITBranchOff = false; bool bJITRegisterCacheOff = false; bool m_enable_debugging = false; + bool m_enable_branch_following = false; bool m_enable_float_exceptions = false; bool m_enable_div_by_zero_exceptions = false; bool m_low_dcbz_hack = false; @@ -159,6 +163,9 @@ protected: bool m_cleanup_after_stackfault = false; u8* m_stack_guard = nullptr; + static const std::array*>, 22> JIT_SETTINGS; + + bool DoesConfigNeedRefresh(); void RefreshConfig(); void InitBLROptimization(); @@ -168,8 +175,6 @@ protected: bool CanMergeNextInstructions(int count) const; - void UpdateMemoryAndExceptionOptions(); - bool ShouldHandleFPExceptionForInstruction(const PPCAnalyst::CodeOp* op); public: diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index 664e40dd05..7fd00a8edc 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -854,10 +854,8 @@ void MenuBar::AddJITMenu() m_jit_disable_fastmem = m_jit->addAction(tr("Disable Fastmem")); m_jit_disable_fastmem->setCheckable(true); m_jit_disable_fastmem->setChecked(!Config::Get(Config::MAIN_FASTMEM)); - connect(m_jit_disable_fastmem, &QAction::toggled, [this](bool enabled) { - Config::SetBaseOrCurrent(Config::MAIN_FASTMEM, !enabled); - ClearCache(); - }); + connect(m_jit_disable_fastmem, &QAction::toggled, + [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_FASTMEM, !enabled); }); m_jit_clear_cache = m_jit->addAction(tr("Clear Cache"), this, &MenuBar::ClearCache); @@ -873,106 +871,92 @@ void MenuBar::AddJITMenu() m_jit_off = m_jit->addAction(tr("JIT Off (JIT Core)")); m_jit_off->setCheckable(true); m_jit_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_OFF)); - connect(m_jit_off, &QAction::toggled, [this](bool enabled) { - Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_OFF, enabled); - ClearCache(); - }); + connect(m_jit_off, &QAction::toggled, + [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_OFF, enabled); }); m_jit_loadstore_off = m_jit->addAction(tr("JIT LoadStore Off")); m_jit_loadstore_off->setCheckable(true); m_jit_loadstore_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_OFF)); - connect(m_jit_loadstore_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_loadstore_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_LOAD_STORE_OFF, enabled); - ClearCache(); }); m_jit_loadstore_lbzx_off = m_jit->addAction(tr("JIT LoadStore lbzx Off")); m_jit_loadstore_lbzx_off->setCheckable(true); m_jit_loadstore_lbzx_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_LBZX_OFF)); - connect(m_jit_loadstore_lbzx_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_loadstore_lbzx_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_LOAD_STORE_LBZX_OFF, enabled); - ClearCache(); }); m_jit_loadstore_lxz_off = m_jit->addAction(tr("JIT LoadStore lXz Off")); m_jit_loadstore_lxz_off->setCheckable(true); m_jit_loadstore_lxz_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_LXZ_OFF)); - connect(m_jit_loadstore_lxz_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_loadstore_lxz_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_LOAD_STORE_LXZ_OFF, enabled); - ClearCache(); }); m_jit_loadstore_lwz_off = m_jit->addAction(tr("JIT LoadStore lwz Off")); m_jit_loadstore_lwz_off->setCheckable(true); m_jit_loadstore_lwz_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_LWZ_OFF)); - connect(m_jit_loadstore_lwz_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_loadstore_lwz_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_LOAD_STORE_LWZ_OFF, enabled); - ClearCache(); }); m_jit_loadstore_floating_off = m_jit->addAction(tr("JIT LoadStore Floating Off")); m_jit_loadstore_floating_off->setCheckable(true); m_jit_loadstore_floating_off->setChecked( Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_FLOATING_OFF)); - connect(m_jit_loadstore_floating_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_loadstore_floating_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_LOAD_STORE_FLOATING_OFF, enabled); - ClearCache(); }); m_jit_loadstore_paired_off = m_jit->addAction(tr("JIT LoadStore Paired Off")); m_jit_loadstore_paired_off->setCheckable(true); m_jit_loadstore_paired_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_LOAD_STORE_PAIRED_OFF)); - connect(m_jit_loadstore_paired_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_loadstore_paired_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_LOAD_STORE_PAIRED_OFF, enabled); - ClearCache(); }); m_jit_floatingpoint_off = m_jit->addAction(tr("JIT FloatingPoint Off")); m_jit_floatingpoint_off->setCheckable(true); m_jit_floatingpoint_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_FLOATING_POINT_OFF)); - connect(m_jit_floatingpoint_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_floatingpoint_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_FLOATING_POINT_OFF, enabled); - ClearCache(); }); m_jit_integer_off = m_jit->addAction(tr("JIT Integer Off")); m_jit_integer_off->setCheckable(true); m_jit_integer_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_INTEGER_OFF)); - connect(m_jit_integer_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_integer_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_INTEGER_OFF, enabled); - ClearCache(); }); m_jit_paired_off = m_jit->addAction(tr("JIT Paired Off")); m_jit_paired_off->setCheckable(true); m_jit_paired_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_PAIRED_OFF)); - connect(m_jit_paired_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_paired_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_PAIRED_OFF, enabled); - ClearCache(); }); m_jit_systemregisters_off = m_jit->addAction(tr("JIT SystemRegisters Off")); m_jit_systemregisters_off->setCheckable(true); m_jit_systemregisters_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_SYSTEM_REGISTERS_OFF)); - connect(m_jit_systemregisters_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_systemregisters_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_SYSTEM_REGISTERS_OFF, enabled); - ClearCache(); }); m_jit_branch_off = m_jit->addAction(tr("JIT Branch Off")); m_jit_branch_off->setCheckable(true); m_jit_branch_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_BRANCH_OFF)); - connect(m_jit_branch_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_branch_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_BRANCH_OFF, enabled); - ClearCache(); }); m_jit_register_cache_off = m_jit->addAction(tr("JIT Register Cache Off")); m_jit_register_cache_off->setCheckable(true); m_jit_register_cache_off->setChecked(Config::Get(Config::MAIN_DEBUG_JIT_REGISTER_CACHE_OFF)); - connect(m_jit_register_cache_off, &QAction::toggled, [this](bool enabled) { + connect(m_jit_register_cache_off, &QAction::toggled, [](bool enabled) { Config::SetBaseOrCurrent(Config::MAIN_DEBUG_JIT_REGISTER_CACHE_OFF, enabled); - ClearCache(); }); } From 07d70db46b8eb1029eb6543f25900450987303c1 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 10 Apr 2022 11:11:25 +0200 Subject: [PATCH 056/120] DolphinQt: Allow toggling most JIT debug settings at any time There's no reason not to allow this now that these settings are cleanly integrated into the new config system. (Actually, maybe we could even have done this before the previous commit...) --- Source/Core/DolphinQt/MenuBar.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Source/Core/DolphinQt/MenuBar.cpp b/Source/Core/DolphinQt/MenuBar.cpp index 7fd00a8edc..ad8428bee3 100644 --- a/Source/Core/DolphinQt/MenuBar.cpp +++ b/Source/Core/DolphinQt/MenuBar.cpp @@ -139,20 +139,10 @@ void MenuBar::OnEmulationStateChanged(Core::State state) m_jit_interpreter_core->setEnabled(running); m_jit_block_linking->setEnabled(!running); m_jit_disable_cache->setEnabled(!running); - m_jit_disable_fastmem->setEnabled(!running); m_jit_clear_cache->setEnabled(running); m_jit_log_coverage->setEnabled(!running); m_jit_search_instruction->setEnabled(running); - for (QAction* action : - {m_jit_off, m_jit_loadstore_off, m_jit_loadstore_lbzx_off, m_jit_loadstore_lxz_off, - m_jit_loadstore_lwz_off, m_jit_loadstore_floating_off, m_jit_loadstore_paired_off, - m_jit_floatingpoint_off, m_jit_integer_off, m_jit_paired_off, m_jit_systemregisters_off, - m_jit_branch_off, m_jit_register_cache_off}) - { - action->setEnabled(running && !playing); - } - // Symbols m_symbols->setEnabled(running); From 85281e76ee24f2aec68b23d8a89359a71a5cf5de Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 10 Apr 2022 11:32:33 +0200 Subject: [PATCH 057/120] Jit: Remove unnecessary member variables --- Source/Core/Core/PowerPC/JitArm64/Jit.cpp | 2 +- Source/Core/Core/PowerPC/JitCommon/JitBase.cpp | 4 +--- Source/Core/Core/PowerPC/JitCommon/JitBase.h | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp index 99d5da4b6f..feda149107 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp @@ -49,7 +49,7 @@ void JitArm64::Init() { RefreshConfig(); - const size_t child_code_size = m_mmu_enabled ? FARCODE_SIZE_MMU : FARCODE_SIZE; + const size_t child_code_size = jo.memcheck ? FARCODE_SIZE_MMU : FARCODE_SIZE; AllocCodeSpace(CODE_SIZE + child_code_size); AddChildCodeSpace(&m_far_code, child_code_size); diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp index 58d61499dc..7c9c707fbc 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp @@ -119,8 +119,6 @@ void JitBase::RefreshConfig() for (const auto& [member, config_info] : JIT_SETTINGS) this->*member = Config::Get(*config_info); - m_mmu_enabled = m_system.IsMMUMode(); - m_pause_on_panic_enabled = m_system.IsPauseOnPanicMode(); if (m_accurate_cpu_cache_enabled) { m_fastmem_enabled = false; @@ -135,7 +133,7 @@ void JitBase::RefreshConfig() bool any_watchpoints = m_system.GetPowerPC().GetMemChecks().HasAny(); jo.fastmem = m_fastmem_enabled && jo.fastmem_arena && (m_ppc_state.msr.DR || !any_watchpoints); - jo.memcheck = m_mmu_enabled || m_pause_on_panic_enabled || any_watchpoints; + jo.memcheck = m_system.IsMMUMode() || m_system.IsPauseOnPanicMode() || any_watchpoints; jo.fp_exceptions = m_enable_float_exceptions; jo.div_by_zero_exceptions = m_enable_div_by_zero_exceptions; } diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.h b/Source/Core/Core/PowerPC/JitCommon/JitBase.h index 2846938cf4..6c95559438 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.h @@ -155,8 +155,6 @@ protected: bool m_fprf = false; bool m_accurate_nans = false; bool m_fastmem_enabled = false; - bool m_mmu_enabled = false; - bool m_pause_on_panic_enabled = false; bool m_accurate_cpu_cache_enabled = false; bool m_enable_blr_optimization = false; From 7daa19f40d75149a9d5a828bf8d801e37b8fe317 Mon Sep 17 00:00:00 2001 From: Frajo Haider Date: Thu, 3 Aug 2023 23:35:00 +0300 Subject: [PATCH 058/120] JitArm64: Avoid loading compilerPC multiple times if it's already in a register. --- Source/Core/Core/PowerPC/JitArm64/Jit.cpp | 52 +++++++++++++++---- Source/Core/Core/PowerPC/JitArm64/Jit.h | 12 +++-- .../Core/PowerPC/JitArm64/JitArm64_Branch.cpp | 45 ++++++++++------ 3 files changed, 80 insertions(+), 29 deletions(-) diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp index 664f8a6525..f8de18d195 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp @@ -374,7 +374,8 @@ void JitArm64::EmitStoreMembase(const ARM64Reg& msr) gpr.Unlock(WD); } -void JitArm64::WriteExit(u32 destination, bool LK, u32 exit_address_after_return) +void JitArm64::WriteExit(u32 destination, bool LK, u32 exit_address_after_return, + ARM64Reg exit_address_after_return_reg) { Cleanup(); EndTimeProfile(js.curBlock); @@ -386,11 +387,16 @@ void JitArm64::WriteExit(u32 destination, bool LK, u32 exit_address_after_return if (LK) { // Push {ARM_PC; PPC_PC} on the stack - MOVI2R(ARM64Reg::X1, exit_address_after_return); + ARM64Reg reg_to_push = exit_address_after_return_reg; + if (exit_address_after_return_reg == ARM64Reg::INVALID_REG) + { + MOVI2R(ARM64Reg::X1, exit_address_after_return); + reg_to_push = ARM64Reg::X1; + } constexpr s32 adr_offset = JitArm64BlockCache::BLOCK_LINK_SIZE + sizeof(u32) * 2; host_address_after_return = GetCodePtr() + adr_offset; ADR(ARM64Reg::X0, adr_offset); - STP(IndexType::Pre, ARM64Reg::X0, ARM64Reg::X1, ARM64Reg::SP, -16); + STP(IndexType::Pre, ARM64Reg::X0, reg_to_push, ARM64Reg::SP, -16); } constexpr size_t primary_farcode_size = 3 * sizeof(u32); @@ -457,7 +463,8 @@ void JitArm64::WriteExit(u32 destination, bool LK, u32 exit_address_after_return SwitchToNearCode(); } -void JitArm64::WriteExit(Arm64Gen::ARM64Reg dest, bool LK, u32 exit_address_after_return) +void JitArm64::WriteExit(Arm64Gen::ARM64Reg dest, bool LK, u32 exit_address_after_return, + ARM64Reg exit_address_after_return_reg) { if (dest != DISPATCHER_PC) MOV(DISPATCHER_PC, dest); @@ -475,11 +482,17 @@ void JitArm64::WriteExit(Arm64Gen::ARM64Reg dest, bool LK, u32 exit_address_afte else { // Push {ARM_PC, PPC_PC} on the stack + ARM64Reg reg_to_push = exit_address_after_return_reg; + if (exit_address_after_return_reg == ARM64Reg::INVALID_REG) + { + MOVI2R(ARM64Reg::X1, exit_address_after_return); + reg_to_push = ARM64Reg::X1; + } MOVI2R(ARM64Reg::X1, exit_address_after_return); constexpr s32 adr_offset = sizeof(u32) * 3; const u8* host_address_after_return = GetCodePtr() + adr_offset; ADR(ARM64Reg::X0, adr_offset); - STP(IndexType::Pre, ARM64Reg::X0, ARM64Reg::X1, ARM64Reg::SP, -16); + STP(IndexType::Pre, ARM64Reg::X0, reg_to_push, ARM64Reg::SP, -16); BL(dispatcher); DEBUG_ASSERT(GetCodePtr() == host_address_after_return || HasWriteFailed()); @@ -515,26 +528,43 @@ void JitArm64::WriteExit(Arm64Gen::ARM64Reg dest, bool LK, u32 exit_address_afte } } -void JitArm64::FakeLKExit(u32 exit_address_after_return) +void JitArm64::FakeLKExit(u32 exit_address_after_return, ARM64Reg exit_address_after_return_reg) { if (!m_enable_blr_optimization) return; // We may need to fake the BLR stack on inlined CALL instructions. // Else we can't return to this location any more. - gpr.Lock(ARM64Reg::W30); - ARM64Reg after_reg = gpr.GetReg(); + if (exit_address_after_return_reg != ARM64Reg::W30) + { + // Do not lock W30 if it is the same as the exit address register, since + // it's already locked. It'll only get clobbered at the BL (below) where + // we do not need its value anymore. + // NOTE: This means W30 won't contain the return address anymore after this + // function has been called! + gpr.Lock(ARM64Reg::W30); + } + ARM64Reg after_reg = exit_address_after_return_reg; + if (exit_address_after_return_reg == ARM64Reg::INVALID_REG) + { + after_reg = gpr.GetReg(); + MOVI2R(after_reg, exit_address_after_return); + } ARM64Reg code_reg = gpr.GetReg(); - MOVI2R(after_reg, exit_address_after_return); constexpr s32 adr_offset = sizeof(u32) * 3; const u8* host_address_after_return = GetCodePtr() + adr_offset; ADR(EncodeRegTo64(code_reg), adr_offset); STP(IndexType::Pre, EncodeRegTo64(code_reg), EncodeRegTo64(after_reg), ARM64Reg::SP, -16); - gpr.Unlock(after_reg, code_reg); + gpr.Unlock(code_reg); + if (after_reg != exit_address_after_return_reg) + gpr.Unlock(after_reg); FixupBranch skip_exit = BL(); DEBUG_ASSERT(GetCodePtr() == host_address_after_return || HasWriteFailed()); - gpr.Unlock(ARM64Reg::W30); + if (exit_address_after_return_reg != ARM64Reg::W30) + { + gpr.Unlock(ARM64Reg::W30); + } // Write the regular exit node after the return. JitBlock* b = js.curBlock; diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.h b/Source/Core/Core/PowerPC/JitArm64/Jit.h index df71e70a93..7025ffaa2d 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.h +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.h @@ -315,8 +315,12 @@ protected: void EmitStoreMembase(const Arm64Gen::ARM64Reg& msr); // Exits - void WriteExit(u32 destination, bool LK = false, u32 exit_address_after_return = 0); - void WriteExit(Arm64Gen::ARM64Reg dest, bool LK = false, u32 exit_address_after_return = 0); + void + WriteExit(u32 destination, bool LK = false, u32 exit_address_after_return = 0, + Arm64Gen::ARM64Reg exit_address_after_return_reg = Arm64Gen::ARM64Reg::INVALID_REG); + void + WriteExit(Arm64Gen::ARM64Reg dest, bool LK = false, u32 exit_address_after_return = 0, + Arm64Gen::ARM64Reg exit_address_after_return_reg = Arm64Gen::ARM64Reg::INVALID_REG); void WriteExceptionExit(u32 destination, bool only_external = false, bool always_exception = false); void WriteExceptionExit(Arm64Gen::ARM64Reg dest, bool only_external = false, @@ -325,7 +329,9 @@ protected: void WriteConditionalExceptionExit(int exception, Arm64Gen::ARM64Reg temp_gpr, Arm64Gen::ARM64Reg temp_fpr = Arm64Gen::ARM64Reg::INVALID_REG, u64 increment_sp_on_exit = 0); - void FakeLKExit(u32 exit_address_after_return); + void + FakeLKExit(u32 exit_address_after_return, + Arm64Gen::ARM64Reg exit_address_after_return_reg = Arm64Gen::ARM64Reg::INVALID_REG); void WriteBLRExit(Arm64Gen::ARM64Reg dest); Arm64Gen::FixupBranch JumpIfCRFieldBit(int field, int bit, bool jump_if_set); diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64_Branch.cpp b/Source/Core/Core/PowerPC/JitArm64/JitArm64_Branch.cpp index edb31f7d64..62f04584ec 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitArm64_Branch.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64_Branch.cpp @@ -79,12 +79,12 @@ void JitArm64::bx(UGeckoInstruction inst) INSTRUCTION_START JITDISABLE(bJITBranchOff); + ARM64Reg WA = ARM64Reg::INVALID_REG; if (inst.LK) { - ARM64Reg WA = gpr.GetReg(); + WA = gpr.GetReg(); MOVI2R(WA, js.compilerPC + 4); STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_LR)); - gpr.Unlock(WA); } if (!js.isLastInstruction) @@ -94,8 +94,12 @@ void JitArm64::bx(UGeckoInstruction inst) // We have to fake the stack as the RET instruction was not // found in the same block. This is a big overhead, but still // better than calling the dispatcher. - FakeLKExit(js.compilerPC + 4); + FakeLKExit(js.compilerPC + 4, WA); } + + if (WA != ARM64Reg::INVALID_REG) + gpr.Unlock(WA); + return; } @@ -104,19 +108,24 @@ void JitArm64::bx(UGeckoInstruction inst) if (js.op->branchIsIdleLoop) { - // make idle loops go faster - ARM64Reg WA = gpr.GetReg(); - ARM64Reg XA = EncodeRegTo64(WA); + if (WA != ARM64Reg::INVALID_REG) + gpr.Unlock(WA); - MOVP2R(XA, &CoreTiming::GlobalIdle); - BLR(XA); - gpr.Unlock(WA); + // make idle loops go faster + ARM64Reg WB = gpr.GetReg(); + ARM64Reg XB = EncodeRegTo64(WB); + + MOVP2R(XB, &CoreTiming::GlobalIdle); + BLR(XB); + gpr.Unlock(WB); WriteExceptionExit(js.op->branchTo); return; } - WriteExit(js.op->branchTo, inst.LK, js.compilerPC + 4); + WriteExit(js.op->branchTo, inst.LK, js.compilerPC + 4, inst.LK ? WA : ARM64Reg::INVALID_REG); + if (WA != ARM64Reg::INVALID_REG) + gpr.Unlock(WA); } void JitArm64::bcx(UGeckoInstruction inst) @@ -125,6 +134,8 @@ void JitArm64::bcx(UGeckoInstruction inst) JITDISABLE(bJITBranchOff); ARM64Reg WA = gpr.GetReg(); + ARM64Reg WB = inst.LK ? gpr.GetReg() : WA; + FixupBranch pCTRDontBranch; if ((inst.BO & BO_DONT_DECREMENT_FLAG) == 0) // Decrement and test CTR { @@ -156,7 +167,7 @@ void JitArm64::bcx(UGeckoInstruction inst) STR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_LR)); } - gpr.Flush(FlushMode::MaintainState, WA); + gpr.Flush(FlushMode::MaintainState, WB); fpr.Flush(FlushMode::MaintainState, ARM64Reg::INVALID_REG); if (js.op->branchIsIdleLoop) @@ -171,7 +182,7 @@ void JitArm64::bcx(UGeckoInstruction inst) } else { - WriteExit(js.op->branchTo, inst.LK, js.compilerPC + 4); + WriteExit(js.op->branchTo, inst.LK, js.compilerPC + 4, inst.LK ? WA : ARM64Reg::INVALID_REG); } SwitchToNearCode(); @@ -189,6 +200,8 @@ void JitArm64::bcx(UGeckoInstruction inst) } gpr.Unlock(WA); + if (WB != WA) + gpr.Unlock(WB); } void JitArm64::bcctrx(UGeckoInstruction inst) @@ -211,12 +224,12 @@ void JitArm64::bcctrx(UGeckoInstruction inst) gpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG); fpr.Flush(FlushMode::All, ARM64Reg::INVALID_REG); + ARM64Reg WB = ARM64Reg::INVALID_REG; if (inst.LK_3) { - ARM64Reg WB = gpr.GetReg(); + WB = gpr.GetReg(); MOVI2R(WB, js.compilerPC + 4); STR(IndexType::Unsigned, WB, PPC_REG, PPCSTATE_OFF_SPR(SPR_LR)); - gpr.Unlock(WB); } ARM64Reg WA = gpr.GetReg(); @@ -224,8 +237,10 @@ void JitArm64::bcctrx(UGeckoInstruction inst) LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF_SPR(SPR_CTR)); AND(WA, WA, LogicalImm(~0x3, 32)); - WriteExit(WA, inst.LK_3, js.compilerPC + 4); + WriteExit(WA, inst.LK_3, js.compilerPC + 4, inst.LK_3 ? WB : ARM64Reg::INVALID_REG); + if (WB != ARM64Reg::INVALID_REG) + gpr.Unlock(WB); gpr.Unlock(WA); } From f982c556b5d7779d2c16f91be9c7683f59939fa3 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Sat, 26 Aug 2023 12:12:37 -0500 Subject: [PATCH 059/120] VideoCommon: add additional texture sampler types to ShaderAsset --- Source/Core/VideoCommon/Assets/ShaderAsset.cpp | 8 ++++++++ Source/Core/VideoCommon/Assets/ShaderAsset.h | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/Source/Core/VideoCommon/Assets/ShaderAsset.cpp b/Source/Core/VideoCommon/Assets/ShaderAsset.cpp index d09e572bab..af5dad4a93 100644 --- a/Source/Core/VideoCommon/Assets/ShaderAsset.cpp +++ b/Source/Core/VideoCommon/Assets/ShaderAsset.cpp @@ -49,6 +49,14 @@ bool ParseShaderProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset { property.m_type = ShaderProperty::Type::Type_Sampler2D; } + else if (type == "samplerarrayshared_main") + { + property.m_type = ShaderProperty::Type::Type_SamplerArrayShared_Main; + } + else if (type == "samplerarrayshared_additional") + { + property.m_type = ShaderProperty::Type::Type_SamplerArrayShared_Additional; + } else { ERROR_LOG_FMT(VIDEO, diff --git a/Source/Core/VideoCommon/Assets/ShaderAsset.h b/Source/Core/VideoCommon/Assets/ShaderAsset.h index 98712f0d78..0675adff02 100644 --- a/Source/Core/VideoCommon/Assets/ShaderAsset.h +++ b/Source/Core/VideoCommon/Assets/ShaderAsset.h @@ -14,9 +14,17 @@ namespace VideoCommon { struct ShaderProperty { + // "SamplerShared" denotes that the sampler + // already exists outside of the shader source + // (ex: in the Dolphin defined pixel shader) + // "Main" is the first entry in a shared sampler array + // and "Additional" denotes a subsequent entry + // in the array enum class Type { Type_Undefined, + Type_SamplerArrayShared_Main, + Type_SamplerArrayShared_Additional, Type_Sampler2D, Type_Max = Type_Sampler2D }; From 58f247290fdc23015993b62658a40fea9734f095 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 27 Aug 2023 15:35:05 +0200 Subject: [PATCH 060/120] Use latest resolution value for resolution hotkey OSD Because CPU thread config changed callbacks are no longer instant, g_Config.iEFBScale doesn't yet contain the new value when the hotkey OSD code tries to read it. Should fix https://bugs.dolphin-emu.org/issues/13343. --- Source/Core/DolphinQt/HotkeyScheduler.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Core/DolphinQt/HotkeyScheduler.cpp b/Source/Core/DolphinQt/HotkeyScheduler.cpp index 3f42cd4a68..59f8db8433 100644 --- a/Source/Core/DolphinQt/HotkeyScheduler.cpp +++ b/Source/Core/DolphinQt/HotkeyScheduler.cpp @@ -359,8 +359,8 @@ void HotkeyScheduler::Run() // Graphics const auto efb_scale = Config::Get(Config::GFX_EFB_SCALE); - auto ShowEFBScale = []() { - switch (Config::Get(Config::GFX_EFB_SCALE)) + const auto ShowEFBScale = [](int new_efb_scale) { + switch (new_efb_scale) { case EFB_SCALE_AUTO_INTEGRAL: OSD::AddMessage("Internal Resolution: Auto (integral)"); @@ -369,7 +369,7 @@ void HotkeyScheduler::Run() OSD::AddMessage("Internal Resolution: Native"); break; default: - OSD::AddMessage(fmt::format("Internal Resolution: {}x", g_Config.iEFBScale)); + OSD::AddMessage(fmt::format("Internal Resolution: {}x", new_efb_scale)); break; } }; @@ -377,14 +377,14 @@ void HotkeyScheduler::Run() if (IsHotkey(HK_INCREASE_IR)) { Config::SetCurrent(Config::GFX_EFB_SCALE, efb_scale + 1); - ShowEFBScale(); + ShowEFBScale(efb_scale + 1); } if (IsHotkey(HK_DECREASE_IR)) { if (efb_scale > EFB_SCALE_AUTO_INTEGRAL) { Config::SetCurrent(Config::GFX_EFB_SCALE, efb_scale - 1); - ShowEFBScale(); + ShowEFBScale(efb_scale - 1); } } From ee395bb2e59084387e95ab8934d5e3afaa45ca83 Mon Sep 17 00:00:00 2001 From: Charles Oliner Date: Fri, 18 Aug 2023 23:58:02 -0400 Subject: [PATCH 061/120] Added latching buttons Added latching buttons, buttons which turn on when you press them and off when you press them again. --- .../activities/EmulationActivity.kt | 82 +++++++++ .../features/settings/model/BooleanSetting.kt | 168 ++++++++++++++++++ .../dolphinemu/overlay/InputOverlay.kt | 105 +++++++---- .../overlay/InputOverlayDrawableButton.kt | 8 +- .../res/menu/menu_overlay_controls_gc.xml | 4 + .../res/menu/menu_overlay_controls_wii.xml | 4 + .../app/src/main/res/values/arrays.xml | 47 +++++ .../app/src/main/res/values/strings.xml | 1 + 8 files changed, 384 insertions(+), 35 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt index 8ad871a57d..def62eb7ec 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/EmulationActivity.kt @@ -439,6 +439,7 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { MENU_ACTION_EDIT_CONTROLS_PLACEMENT -> editControlsPlacement() MENU_ACTION_RESET_OVERLAY -> resetOverlay() MENU_ACTION_TOGGLE_CONTROLS -> toggleControls() + MENU_ACTION_LATCHING_CONTROLS -> latchingControls() MENU_ACTION_ADJUST_SCALE -> adjustScale() MENU_ACTION_CHOOSE_CONTROLLER -> chooseController() MENU_ACTION_REFRESH_WIIMOTES -> NativeLibrary.RefreshWiimotes() @@ -514,6 +515,85 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { return super.dispatchKeyEvent(event) } + private fun latchingControls() { + val builder = MaterialAlertDialogBuilder(this) + .setTitle(R.string.emulation_latching_controls) + + when (InputOverlay.configuredControllerType) { + InputOverlay.OVERLAY_GAMECUBE -> { + val gcLatchingButtons = BooleanArray(8) + val gcSettingBase = "MAIN_BUTTON_LATCHING_GC_" + + for (i in gcLatchingButtons.indices) { + gcLatchingButtons[i] = BooleanSetting.valueOf(gcSettingBase + i).boolean + } + + builder.setMultiChoiceItems( + R.array.gcpadLatchableButtons, gcLatchingButtons + ) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean -> + BooleanSetting.valueOf(gcSettingBase + indexSelected) + .setBoolean(settings, isChecked) + } + } + InputOverlay.OVERLAY_WIIMOTE_CLASSIC -> { + val wiiClassicLatchingButtons = BooleanArray(11) + val classicSettingBase = "MAIN_BUTTON_LATCHING_CLASSIC_" + + for (i in wiiClassicLatchingButtons.indices) { + wiiClassicLatchingButtons[i] = BooleanSetting.valueOf(classicSettingBase + i).boolean + } + builder.setMultiChoiceItems( + R.array.classicLatchableButtons, wiiClassicLatchingButtons + ) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean -> + BooleanSetting.valueOf(classicSettingBase + indexSelected) + .setBoolean(settings, isChecked) + } + } + InputOverlay.OVERLAY_WIIMOTE_NUNCHUK -> { + val nunchukLatchingButtons = BooleanArray(9) + val nunchukSettingBase = "MAIN_BUTTON_LATCHING_WII_" + + // For OVERLAY_WIIMOTE_NUNCHUK, settings index 7 is the D-Pad (which cannot be + // latching). C and Z (settings indices 8 and 9) need to map to multichoice array + // indices 7 and 8 to avoid a gap. + fun translateToSettingsIndex(idx: Int): Int = if (idx >= 7) idx + 1 else idx + + for (i in nunchukLatchingButtons.indices) { + nunchukLatchingButtons[i] = BooleanSetting + .valueOf(nunchukSettingBase + translateToSettingsIndex(i)).boolean + } + + builder.setMultiChoiceItems( + R.array.nunchukLatchableButtons, nunchukLatchingButtons + ) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean -> + BooleanSetting.valueOf(nunchukSettingBase + translateToSettingsIndex(indexSelected)) + .setBoolean(settings, isChecked) + } + } + else -> { + val wiimoteLatchingButtons = BooleanArray(7) + val wiimoteSettingBase = "MAIN_BUTTON_LATCHING_WII_" + + for (i in wiimoteLatchingButtons.indices) { + wiimoteLatchingButtons[i] = BooleanSetting.valueOf(wiimoteSettingBase + i).boolean + } + + builder.setMultiChoiceItems( + R.array.wiimoteLatchableButtons, wiimoteLatchingButtons + ) { _: DialogInterface?, indexSelected: Int, isChecked: Boolean -> + BooleanSetting.valueOf(wiimoteSettingBase + indexSelected) + .setBoolean(settings, isChecked) + } + } + } + + builder + .setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int -> + emulationFragment?.refreshInputOverlay() + } + .show() + } + private fun toggleControls() { val builder = MaterialAlertDialogBuilder(this) .setTitle(R.string.emulation_toggle_controls) @@ -968,11 +1048,13 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider { const val MENU_ACTION_SETTINGS = 35 const val MENU_ACTION_SKYLANDERS = 36 const val MENU_ACTION_INFINITY_BASE = 37 + const val MENU_ACTION_LATCHING_CONTROLS = 38 init { buttonsActionsMap.apply { append(R.id.menu_emulation_edit_layout, MENU_ACTION_EDIT_CONTROLS_PLACEMENT) append(R.id.menu_emulation_toggle_controls, MENU_ACTION_TOGGLE_CONTROLS) + append(R.id.menu_emulation_latching_controls, MENU_ACTION_LATCHING_CONTROLS) append(R.id.menu_emulation_adjust_scale, MENU_ACTION_ADJUST_SCALE) append(R.id.menu_emulation_choose_controller, MENU_ACTION_CHOOSE_CONTROLLER) append(R.id.menu_emulation_joystick_rel_center, MENU_ACTION_JOYSTICK_REL_CENTER) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt index 60042ceb8e..e32bd1f465 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt @@ -320,6 +320,54 @@ enum class BooleanSetting( "ButtonToggleGCStickC", true ), + MAIN_BUTTON_LATCHING_GC_0( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingGCButtonA", + false + ), + MAIN_BUTTON_LATCHING_GC_1( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingGCButtonB", + false + ), + MAIN_BUTTON_LATCHING_GC_2( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingGCButtonX", + false + ), + MAIN_BUTTON_LATCHING_GC_3( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingGCButtonY", + false + ), + MAIN_BUTTON_LATCHING_GC_4( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingGCButtonZ", + false + ), + MAIN_BUTTON_LATCHING_GC_5( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingGCButtonStart", + false + ), + MAIN_BUTTON_LATCHING_GC_6( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingGCTriggerL", + false + ), + MAIN_BUTTON_LATCHING_GC_7( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingGCTriggerR", + false + ), MAIN_BUTTON_TOGGLE_CLASSIC_0( Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, @@ -404,6 +452,72 @@ enum class BooleanSetting( "ButtonToggleClassicStickRight", true ), + MAIN_BUTTON_LATCHING_CLASSIC_0( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingClassicButtonA", + false + ), + MAIN_BUTTON_LATCHING_CLASSIC_1( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingClassicButtonB", + false + ), + MAIN_BUTTON_LATCHING_CLASSIC_2( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingClassicButtonX", + false + ), + MAIN_BUTTON_LATCHING_CLASSIC_3( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingClassicButtonY", + false + ), + MAIN_BUTTON_LATCHING_CLASSIC_4( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingClassicButtonPlus", + false + ), + MAIN_BUTTON_LATCHING_CLASSIC_5( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingClassicButtonMinus", + false + ), + MAIN_BUTTON_LATCHING_CLASSIC_6( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingClassicButtonHome", + false + ), + MAIN_BUTTON_LATCHING_CLASSIC_7( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingClassicTriggerL", + false + ), + MAIN_BUTTON_LATCHING_CLASSIC_8( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingClassicTriggerR", + false + ), + MAIN_BUTTON_LATCHING_CLASSIC_9( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingClassicButtonZL", + false + ), + MAIN_BUTTON_LATCHING_CLASSIC_10( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingClassicButtonZR", + false + ), MAIN_BUTTON_TOGGLE_WII_0( Settings.FILE_DOLPHIN, Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, @@ -470,6 +584,60 @@ enum class BooleanSetting( "ButtonToggleNunchukStick", true ), + MAIN_BUTTON_LATCHING_WII_0( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingWiimoteButtonA", + false + ), + MAIN_BUTTON_LATCHING_WII_1( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingWiimoteButtonB", + false + ), + MAIN_BUTTON_LATCHING_WII_2( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingWiimoteButton1", + false + ), + MAIN_BUTTON_LATCHING_WII_3( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingWiimoteButton2", + false + ), + MAIN_BUTTON_LATCHING_WII_4( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingWiimoteButtonPlus", + false + ), + MAIN_BUTTON_LATCHING_WII_5( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingWiimoteButtonMinus", + false + ), + MAIN_BUTTON_LATCHING_WII_6( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingWiimoteButtonHome", + false + ), + MAIN_BUTTON_LATCHING_WII_8( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingNunchukC", + false + ), + MAIN_BUTTON_LATCHING_WII_9( + Settings.FILE_DOLPHIN, + Settings.SECTION_INI_ANDROID_OVERLAY_BUTTONS, + "ButtonLatchingNunchukZ", + false + ), SYSCONF_SCREENSAVER(Settings.FILE_SYSCONF, "IPL", "SSV", false), SYSCONF_WIDESCREEN(Settings.FILE_SYSCONF, "IPL", "AR", true), SYSCONF_PROGRESSIVE_SCAN(Settings.FILE_SYSCONF, "IPL", "PGS", true), diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt index cc974a89df..dfe557e712 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlay.kt @@ -154,10 +154,10 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex event.getY(pointerIndex).toInt() ) ) { - button.setPressedState(true) + button.setPressedState(if (button.latching) !button.getPressedState() else true) button.trackId = event.getPointerId(pointerIndex) pressed = true - InputOverrider.setControlState(controllerIndex, button.control, 1.0) + InputOverrider.setControlState(controllerIndex, button.control, if (button.getPressedState()) 1.0 else 0.0) val analogControl = getAnalogControlForTrigger(button.control) if (analogControl >= 0) @@ -173,8 +173,9 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex MotionEvent.ACTION_POINTER_UP -> { // If a pointer ends, release the button it was pressing. if (button.trackId == event.getPointerId(pointerIndex)) { - button.setPressedState(false) - InputOverrider.setControlState(controllerIndex, button.control, 0.0) + if (!button.latching) + button.setPressedState(false) + InputOverrider.setControlState(controllerIndex, button.control, if (button.getPressedState()) 1.0 else 0.0) val analogControl = getAnalogControlForTrigger(button.control) if (analogControl >= 0) @@ -497,7 +498,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.gcpad_a_pressed, ButtonType.BUTTON_A, ControlId.GCPAD_A_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_GC_0.boolean ) ) } @@ -509,7 +511,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.gcpad_b_pressed, ButtonType.BUTTON_B, ControlId.GCPAD_B_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_GC_1.boolean ) ) } @@ -521,7 +524,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.gcpad_x_pressed, ButtonType.BUTTON_X, ControlId.GCPAD_X_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_GC_2.boolean ) ) } @@ -533,7 +537,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.gcpad_y_pressed, ButtonType.BUTTON_Y, ControlId.GCPAD_Y_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_GC_3.boolean ) ) } @@ -545,7 +550,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.gcpad_z_pressed, ButtonType.BUTTON_Z, ControlId.GCPAD_Z_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_GC_4.boolean ) ) } @@ -557,7 +563,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.gcpad_start_pressed, ButtonType.BUTTON_START, ControlId.GCPAD_START_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_GC_5.boolean ) ) } @@ -569,7 +576,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.gcpad_l_pressed, ButtonType.TRIGGER_L, ControlId.GCPAD_L_DIGITAL, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_GC_6.boolean ) ) } @@ -581,7 +589,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.gcpad_r_pressed, ButtonType.TRIGGER_R, ControlId.GCPAD_R_DIGITAL, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_GC_7.boolean ) ) } @@ -640,7 +649,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.wiimote_a_pressed, ButtonType.WIIMOTE_BUTTON_A, ControlId.WIIMOTE_A_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_WII_0.boolean ) ) } @@ -652,7 +662,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.wiimote_b_pressed, ButtonType.WIIMOTE_BUTTON_B, ControlId.WIIMOTE_B_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_WII_1.boolean ) ) } @@ -664,7 +675,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.wiimote_one_pressed, ButtonType.WIIMOTE_BUTTON_1, ControlId.WIIMOTE_ONE_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_WII_2.boolean ) ) } @@ -676,7 +688,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.wiimote_two_pressed, ButtonType.WIIMOTE_BUTTON_2, ControlId.WIIMOTE_TWO_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_WII_3.boolean ) ) } @@ -688,7 +701,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.wiimote_plus_pressed, ButtonType.WIIMOTE_BUTTON_PLUS, ControlId.WIIMOTE_PLUS_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_WII_4.boolean ) ) } @@ -700,7 +714,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.wiimote_minus_pressed, ButtonType.WIIMOTE_BUTTON_MINUS, ControlId.WIIMOTE_MINUS_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_WII_5.boolean ) ) } @@ -712,7 +727,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.wiimote_home_pressed, ButtonType.WIIMOTE_BUTTON_HOME, ControlId.WIIMOTE_HOME_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_WII_6.boolean ) ) } @@ -743,7 +759,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.nunchuk_c_pressed, ButtonType.NUNCHUK_BUTTON_C, ControlId.NUNCHUK_C_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_WII_8.boolean ) ) } @@ -755,7 +772,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.nunchuk_z_pressed, ButtonType.NUNCHUK_BUTTON_Z, ControlId.NUNCHUK_Z_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_WII_9.boolean ) ) } @@ -784,7 +802,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.classic_a_pressed, ButtonType.CLASSIC_BUTTON_A, ControlId.CLASSIC_A_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_CLASSIC_0.boolean ) ) } @@ -796,7 +815,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.classic_b_pressed, ButtonType.CLASSIC_BUTTON_B, ControlId.CLASSIC_B_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_CLASSIC_1.boolean ) ) } @@ -808,7 +828,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.classic_x_pressed, ButtonType.CLASSIC_BUTTON_X, ControlId.CLASSIC_X_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_CLASSIC_2.boolean ) ) } @@ -820,7 +841,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.classic_y_pressed, ButtonType.CLASSIC_BUTTON_Y, ControlId.CLASSIC_Y_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_CLASSIC_3.boolean ) ) } @@ -832,7 +854,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.wiimote_plus_pressed, ButtonType.CLASSIC_BUTTON_PLUS, ControlId.CLASSIC_PLUS_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_CLASSIC_4.boolean ) ) } @@ -844,7 +867,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.wiimote_minus_pressed, ButtonType.CLASSIC_BUTTON_MINUS, ControlId.CLASSIC_MINUS_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_CLASSIC_5.boolean ) ) } @@ -856,7 +880,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.wiimote_home_pressed, ButtonType.CLASSIC_BUTTON_HOME, ControlId.CLASSIC_HOME_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_CLASSIC_6.boolean ) ) } @@ -868,7 +893,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.classic_l_pressed, ButtonType.CLASSIC_TRIGGER_L, ControlId.CLASSIC_L_DIGITAL, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_CLASSIC_7.boolean ) ) } @@ -880,7 +906,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.classic_r_pressed, ButtonType.CLASSIC_TRIGGER_R, ControlId.CLASSIC_R_DIGITAL, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_CLASSIC_8.boolean ) ) } @@ -892,7 +919,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.classic_zl_pressed, ButtonType.CLASSIC_BUTTON_ZL, ControlId.CLASSIC_ZL_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_CLASSIC_9.boolean ) ) } @@ -904,7 +932,8 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex R.drawable.classic_zr_pressed, ButtonType.CLASSIC_BUTTON_ZR, ControlId.CLASSIC_ZR_BUTTON, - orientation + orientation, + BooleanSetting.MAIN_BUTTON_LATCHING_CLASSIC_10.boolean ) ) } @@ -1094,11 +1123,17 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State). * @param legacyId Legacy identifier for the button the InputOverlayDrawableButton represents. * @param control Control identifier for the button the InputOverlayDrawableButton represents. + * @param latching Whether the button is latching. * @return An [InputOverlayDrawableButton] with the correct drawing bounds set. */ private fun initializeOverlayButton( context: Context, - defaultResId: Int, pressedResId: Int, legacyId: Int, control: Int, orientation: String + defaultResId: Int, + pressedResId: Int, + legacyId: Int, + control: Int, + orientation: String, + latching: Boolean ): InputOverlayDrawableButton { // Decide scale based on button ID and user preference var scale = when (legacyId) { @@ -1140,12 +1175,14 @@ class InputOverlay(context: Context?, attrs: AttributeSet?) : SurfaceView(contex resizeBitmap(context, BitmapFactory.decodeResource(resources, defaultResId), scale) val pressedStateBitmap = resizeBitmap(context, BitmapFactory.decodeResource(resources, pressedResId), scale) + val overlayDrawable = InputOverlayDrawableButton( resources, defaultStateBitmap, pressedStateBitmap, legacyId, - control + control, + latching ) // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableButton.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableButton.kt index 220eb836d5..643d65fe14 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableButton.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/overlay/InputOverlayDrawableButton.kt @@ -18,13 +18,15 @@ import android.view.MotionEvent * @param pressedStateBitmap [Bitmap] to use with the pressed state Drawable. * @param legacyId Legacy identifier (ButtonType) for this type of button. * @param control Control ID for this type of button. + * @param latching Whether this button is latching. */ class InputOverlayDrawableButton( res: Resources, defaultStateBitmap: Bitmap, pressedStateBitmap: Bitmap, val legacyId: Int, - val control: Int + val control: Int, + var latching: Boolean ) { var trackId: Int = -1 private var previousTouchX = 0 @@ -94,4 +96,8 @@ class InputOverlayDrawableButton( fun setPressedState(isPressed: Boolean) { pressedState = isPressed } + + fun getPressedState(): Boolean { + return pressedState; + } } diff --git a/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml b/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml index fc33d4639f..e4ef459487 100644 --- a/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml +++ b/Source/Android/app/src/main/res/menu/menu_overlay_controls_gc.xml @@ -10,6 +10,10 @@ android:id="@+id/menu_emulation_toggle_controls" android:title="@string/emulation_toggle_controls"/> + + diff --git a/Source/Android/app/src/main/res/menu/menu_overlay_controls_wii.xml b/Source/Android/app/src/main/res/menu/menu_overlay_controls_wii.xml index 4c1281f644..066889493f 100644 --- a/Source/Android/app/src/main/res/menu/menu_overlay_controls_wii.xml +++ b/Source/Android/app/src/main/res/menu/menu_overlay_controls_wii.xml @@ -10,6 +10,10 @@ android:id="@+id/menu_emulation_toggle_controls" android:title="@string/emulation_toggle_controls"/> + + diff --git a/Source/Android/app/src/main/res/values/arrays.xml b/Source/Android/app/src/main/res/values/arrays.xml index fd2618af70..3821c0b22d 100644 --- a/Source/Android/app/src/main/res/values/arrays.xml +++ b/Source/Android/app/src/main/res/values/arrays.xml @@ -400,6 +400,17 @@ @string/gamepad_c_stick + + A + B + X + Y + Z + @string/gamepad_start + L + R + + A B @@ -411,6 +422,16 @@ @string/gamepad_d_pad + + A + B + 1 + 2 + + + - + @string/gamepad_home + + A B @@ -425,6 +446,18 @@ @string/gamepad_nunchuk_stick + + A + B + 1 + 2 + + + - + @string/gamepad_home + C + Z + + a b @@ -442,6 +475,20 @@ @string/gamepad_right_stick + + a + b + x + y + + + - + @string/gamepad_home + L + R + ZL + ZR + + @string/ir_disabled @string/ir_follow diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 9145d10af0..059375a344 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -571,6 +571,7 @@ It can efficiently compress both junk data and encrypted Wii data. Edit Layout Done Toggle Controls + Latching Controls Toggle All Scale Opacity From 5b112dbf2c5c6c1025fb811689df06e89a12eed8 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 28 Aug 2023 21:17:01 +0200 Subject: [PATCH 062/120] VideoCommon: Fix GLSL uint handling in UberShaderPixel --- Source/Core/VideoCommon/UberShaderPixel.cpp | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Source/Core/VideoCommon/UberShaderPixel.cpp b/Source/Core/VideoCommon/UberShaderPixel.cpp index bcddde4adc..7e8c07cfb8 100644 --- a/Source/Core/VideoCommon/UberShaderPixel.cpp +++ b/Source/Core/VideoCommon/UberShaderPixel.cpp @@ -499,33 +499,33 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, for (u32 i = 0; i < numTexgen; i++) { out.Write(" case {}u:\n" - " return {};\n", + " return {}u;\n", i, i); } out.Write(" default:\n" - " return 0;\n" + " return 0u;\n" " }}\n"); } else { out.Write(" if (texmap >= {}u) {{\n", numTexgen); - out.Write(" return 0;\n" + out.Write(" return 0u;\n" " }}\n"); if (numTexgen > 4) out.Write(" if (texmap < 4u) {{\n"); if (numTexgen > 2) out.Write(" if (texmap < 2u) {{\n"); if (numTexgen > 1) - out.Write(" return (texmap == 0u) ? 0 : 1;\n"); + out.Write(" return (texmap == 0u) ? 0u : 1u;\n"); else - out.Write(" return 0;\n"); + out.Write(" return 0u;\n"); if (numTexgen > 2) { out.Write(" }} else {{\n"); // >= 2 < min(4, numTexgen) if (numTexgen > 3) - out.Write(" return (texmap == 2u) ? 2 : 3;\n"); + out.Write(" return (texmap == 2u) ? 2u : 3u;\n"); else - out.Write(" return 2;\n"); + out.Write(" return 2u;\n"); out.Write(" }}\n"); } if (numTexgen > 4) @@ -534,16 +534,16 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config, if (numTexgen > 6) out.Write(" if (texmap < 6u) {{\n"); if (numTexgen > 5) - out.Write(" return (texmap == 4u) ? 4 : 5;\n"); + out.Write(" return (texmap == 4u) ? 4u : 5u;\n"); else - out.Write(" return 4;\n"); + out.Write(" return 4u;\n"); if (numTexgen > 6) { out.Write(" }} else {{\n"); // >= 6 < min(8, numTexgen) if (numTexgen > 7) - out.Write(" return (texmap == 6u) ? 6 : 7;\n"); + out.Write(" return (texmap == 6u) ? 6u : 7u;\n"); else - out.Write(" return 6;\n"); + out.Write(" return 6u;\n"); out.Write(" }}\n"); } out.Write(" }}\n"); From 80b329b77f58144e1e1ed1608a0fe6bf779c2610 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Fri, 11 Aug 2023 17:10:03 -0400 Subject: [PATCH 063/120] Android: Expose color correction settings --- .../features/settings/model/BooleanSetting.kt | 12 ++++ .../features/settings/model/FloatSetting.kt | 3 +- .../features/settings/model/IntSetting.kt | 6 ++ .../features/settings/model/Settings.kt | 1 + .../features/settings/ui/SettingsFragment.kt | 1 + .../settings/ui/SettingsFragmentPresenter.kt | 71 ++++++++++++++++--- .../app/src/main/res/values/arrays.xml | 11 +++ .../app/src/main/res/values/strings.xml | 14 ++++ 8 files changed, 109 insertions(+), 10 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt index 60042ceb8e..e0171f9831 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt @@ -595,6 +595,18 @@ enum class BooleanSetting( "ArbitraryMipmapDetection", true ), + GFX_CC_CORRECT_COLOR_SPACE( + Settings.FILE_GFX, + Settings.SECTION_GFX_COLOR_CORRECTION, + "CorrectColorSpace", + false + ), + GFX_CC_CORRECT_GAMMA( + Settings.FILE_GFX, + Settings.SECTION_GFX_COLOR_CORRECTION, + "CorrectGamma", + false + ), GFX_STEREO_SWAP_EYES(Settings.FILE_GFX, Settings.SECTION_STEREOSCOPY, "StereoSwapEyes", false), GFX_HACK_EFB_ACCESS_ENABLE( Settings.FILE_GFX, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/FloatSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/FloatSetting.kt index 99b73251e7..ec5bda11cc 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/FloatSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/FloatSetting.kt @@ -10,7 +10,8 @@ enum class FloatSetting( ) : AbstractFloatSetting { // These entries have the same names and order as in C++, just for consistency. MAIN_EMULATION_SPEED(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "EmulationSpeed", 1.0f), - MAIN_OVERCLOCK(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "Overclock", 1.0f); + MAIN_OVERCLOCK(Settings.FILE_DOLPHIN, Settings.SECTION_INI_CORE, "Overclock", 1.0f), + GFX_CC_GAME_GAMMA(Settings.FILE_GFX, Settings.SECTION_GFX_COLOR_CORRECTION, "GameGamma", 2.35f); override val isOverridden: Boolean get() = NativeConfig.isOverridden(file, section, key) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.kt index 256e68007e..22bb47528b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/IntSetting.kt @@ -119,6 +119,12 @@ enum class IntSetting( "MaxAnisotropy", 0 ), + GFX_CC_GAME_COLOR_SPACE( + Settings.FILE_GFX, + Settings.SECTION_GFX_COLOR_CORRECTION, + "GameColorSpace", + 0 + ), GFX_STEREO_MODE(Settings.FILE_GFX, Settings.SECTION_STEREOSCOPY, "StereoMode", 0), GFX_STEREO_DEPTH(Settings.FILE_GFX, Settings.SECTION_STEREOSCOPY, "StereoDepth", 20), GFX_STEREO_CONVERGENCE_PERCENTAGE( diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt index b363104703..27291718dc 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt @@ -135,6 +135,7 @@ class Settings : Closeable { const val SECTION_LOGGER_OPTIONS = "Options" const val SECTION_GFX_SETTINGS = "Settings" const val SECTION_GFX_ENHANCEMENTS = "Enhancements" + const val SECTION_GFX_COLOR_CORRECTION = "ColorCorrection" const val SECTION_GFX_HACKS = "Hacks" const val SECTION_DEBUG = "Debug" const val SECTION_EMULATED_USB_DEVICES = "EmulatedUSBDevices" diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt index 3b67dbd9e8..8101d9efbf 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt @@ -272,6 +272,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView { titles[MenuTag.DEBUG] = R.string.debug_submenu titles[MenuTag.GRAPHICS] = R.string.graphics_settings titles[MenuTag.ENHANCEMENTS] = R.string.enhancements_submenu + titles[MenuTag.COLOR_CORRECTION] = R.string.color_correction_submenu titles[MenuTag.STEREOSCOPY] = R.string.stereoscopy_submenu titles[MenuTag.HACKS] = R.string.hacks_submenu titles[MenuTag.STATISTICS] = R.string.statistics_submenu diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 0947e4f294..99b95bb7e3 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -72,9 +72,8 @@ class SettingsFragmentPresenter( && GpuDriverHelper.supportsCustomDriverLoading() ) { this.gpuDriver = - GpuDriverHelper.getInstalledDriverMetadata() ?: GpuDriverHelper.getSystemDriverMetadata( - context.applicationContext - ) + GpuDriverHelper.getInstalledDriverMetadata() + ?: GpuDriverHelper.getSystemDriverMetadata(context.applicationContext) } } @@ -713,12 +712,12 @@ class SettingsFragmentPresenter( ) ) sl.add( - SwitchSetting( - context, - BooleanSetting.MAIN_WII_WIILINK_ENABLE, - R.string.wii_enable_wiilink, - R.string.wii_enable_wiilink_description - ) + SwitchSetting( + context, + BooleanSetting.MAIN_WII_WIILINK_ENABLE, + R.string.wii_enable_wiilink, + R.string.wii_enable_wiilink_description + ) ) sl.add( SingleChoiceSetting( @@ -1333,6 +1332,13 @@ class SettingsFragmentPresenter( R.array.textureFilteringValues ) ) + sl.add( + SubmenuSetting( + context, + R.string.color_correction_submenu, + MenuTag.COLOR_CORRECTION + ) + ) val stereoModeValue = IntSetting.GFX_STEREO_MODE.int val anaglyphMode = 3 @@ -1429,6 +1435,53 @@ class SettingsFragmentPresenter( } } + private fun addColorCorrectionSettings(sl: ArrayList) { + sl.apply { + add(HeaderSetting(context, R.string.color_space, 0)) + add( + SwitchSetting( + context, + BooleanSetting.GFX_CC_CORRECT_COLOR_SPACE, + R.string.correct_color_space, + R.string.correct_color_space_description + ) + ) + add( + SingleChoiceSetting( + context, + IntSetting.GFX_CC_GAME_COLOR_SPACE, + R.string.game_color_space, + 0, + R.array.colorSpaceEntries, + R.array.colorSpaceValues + ) + ) + + add(HeaderSetting(context, R.string.gamma, 0)) + add( + FloatSliderSetting( + context, + FloatSetting.GFX_CC_GAME_GAMMA, + R.string.game_gamma, + R.string.game_gamma_description, + 2.2f, + 2.8f, + "", + 0.01f, + true + ) + ) + add( + SwitchSetting( + context, + BooleanSetting.GFX_CC_CORRECT_GAMMA, + R.string.correct_sdr_gamma, + 0 + ) + ) + } + } + private fun addHackSettings(sl: ArrayList) { sl.add(HeaderSetting(context, R.string.embedded_frame_buffer, 0)) sl.add( diff --git a/Source/Android/app/src/main/res/values/arrays.xml b/Source/Android/app/src/main/res/values/arrays.xml index fd2618af70..34fb65e614 100644 --- a/Source/Android/app/src/main/res/values/arrays.xml +++ b/Source/Android/app/src/main/res/values/arrays.xml @@ -685,4 +685,15 @@ 21 22 + + + @string/ntscm_space + @string/ntscj_space + @string/pal_space + + + 0 + 1 + 2 + diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 9145d10af0..17421bb7ed 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -222,6 +222,20 @@ Disables the blending of adjacent rows when copying the EFB. This is known in some games as \"deflickering\" or \"smoothing\". Disabling the filter is usually safe, and may result in a sharper image. Arbitrary Mipmap Detection Enables detection of arbitrary mipmaps, which some games use for special distance-based effects.\nMay have false positives that result in blurry textures at increased internal resolution, such as in games that use very low resolution mipmaps. Disabling this can also reduce stutter in games that frequently load new textures.\n\nIf unsure, leave this checked. + Color Correction + Color Space + Correct Color Space + Converts the colors from the color spaces that GC/Wii were meant to work with to sRGB/Rec.709. + Game Color Space + NTSC-M (SMPTE 170M) + NTSC-J (ARUB TR-89) + PAL (EBU) + Gamma + Game Gamma + NTSC-M and NTSC-J target gamma ~2.2. PAL targets gamma ~2.8.\nNone of the two were necessarily followed by games or TVs. 2.35 is a good generic value for all regions.\nIf a game allows you to chose a gamma value, match it here. + Correct SDR Gamma + SDR Display Gamma Target + Custom Gamma Target Stereoscopy Stereoscopy allows you to get a better feeling of depth if you have the necessary hardware.\nHeavily decreases emulation speed and sometimes causes issues Widescreen Hack From 53fc3446d5bbfe2b30b344e25316ffc2bcb3453b Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Fri, 11 Aug 2023 17:11:15 -0400 Subject: [PATCH 064/120] Android: Support settings with float values We did support float settings before but we never showed anything past the decimal place before. --- .../settings/model/view/FloatSliderSetting.kt | 30 +++++++----- .../settings/model/view/IntSliderSetting.kt | 2 +- .../model/view/PercentSliderSetting.kt | 32 +++++++++---- .../settings/model/view/SliderSetting.kt | 28 ++++++----- .../features/settings/ui/MenuTag.kt | 1 + .../features/settings/ui/SettingsAdapter.kt | 48 ++++++++++++++----- .../settings/ui/SettingsFragmentPresenter.kt | 23 +++++---- .../ui/viewholder/SliderViewHolder.kt | 27 ++++++++--- .../app/src/main/res/values/strings.xml | 3 +- 9 files changed, 132 insertions(+), 62 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt index 5b2ee40fe7..c226e3e47e 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt @@ -6,7 +6,8 @@ import android.content.Context import org.dolphinemu.dolphinemu.features.settings.model.AbstractFloatSetting import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting import org.dolphinemu.dolphinemu.features.settings.model.Settings -import kotlin.math.roundToInt +import java.math.BigDecimal +import java.math.MathContext open class FloatSliderSetting : SliderSetting { var floatSetting: AbstractFloatSetting @@ -19,11 +20,12 @@ open class FloatSliderSetting : SliderSetting { setting: AbstractFloatSetting, titleId: Int, descriptionId: Int, - min: Int, - max: Int, + min: Float, + max: Float, units: String?, - stepSize: Int - ) : super(context, titleId, descriptionId, min, max, units, stepSize) { + stepSize: Float, + showDecimal: Boolean + ) : super(context, titleId, descriptionId, min, max, units, stepSize, showDecimal) { floatSetting = setting } @@ -31,17 +33,21 @@ open class FloatSliderSetting : SliderSetting { setting: AbstractFloatSetting, name: CharSequence, description: CharSequence?, - min: Int, - max: Int, - units: String? - ) : super(name, description, min, max, units) { + min: Float, + max: Float, + units: String?, + showDecimal: Boolean + ) : super(name, description, min, max, units, showDecimal) { floatSetting = setting } - override val selectedValue: Int - get() = floatSetting.float.roundToInt() + override val selectedValue: Float + get() = floatSetting.float open fun setSelectedValue(settings: Settings?, selection: Float) { - floatSetting.setFloat(settings!!, selection) + floatSetting.setFloat( + settings!!, + BigDecimal((selection).toDouble()).round(MathContext(3)).toFloat() + ) } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt index 23bcd49317..09a415fc97 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt @@ -16,7 +16,7 @@ class IntSliderSetting( max: Int, units: String?, stepSize: Int -) : SliderSetting(context, titleId, descriptionId, min, max, units, stepSize) { +) : SliderSetting(context, titleId, descriptionId, min, max, units, stepSize, false) { override val setting: AbstractSetting get() = intSetting diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/PercentSliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/PercentSliderSetting.kt index db6454b0c5..165bdebdc8 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/PercentSliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/PercentSliderSetting.kt @@ -4,24 +4,38 @@ package org.dolphinemu.dolphinemu.features.settings.model.view import android.content.Context import org.dolphinemu.dolphinemu.features.settings.model.AbstractFloatSetting -import org.dolphinemu.dolphinemu.features.settings.model.AbstractSetting import org.dolphinemu.dolphinemu.features.settings.model.Settings -import kotlin.math.roundToInt +import java.math.BigDecimal +import java.math.MathContext class PercentSliderSetting( context: Context, override val setting: AbstractFloatSetting, titleId: Int, descriptionId: Int, - min: Int, - max: Int, + min: Float, + max: Float, units: String?, - stepSize: Int -) : FloatSliderSetting(context, setting, titleId, descriptionId, min, max, units, stepSize) { - override val selectedValue: Int - get() = (floatSetting.float * 100).roundToInt() + stepSize: Float, + showDecimal: Boolean +) : FloatSliderSetting( + context, + setting, + titleId, + descriptionId, + min, + max, + units, + stepSize, + showDecimal +) { + override val selectedValue: Float + get() = (floatSetting.float * 100) override fun setSelectedValue(settings: Settings?, selection: Float) { - floatSetting.setFloat(settings!!, selection / 100) + floatSetting.setFloat( + settings!!, + BigDecimal((selection / 100).toDouble()).round(MathContext(3)).toFloat() + ) } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt index f595c4cea3..d3edccc025 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt @@ -4,44 +4,50 @@ package org.dolphinemu.dolphinemu.features.settings.model.view import android.content.Context -abstract class SliderSetting : SettingsItem { +sealed class SliderSetting : SettingsItem { override val type: Int = TYPE_SLIDER - var min: Int + var min: Any private set - var max: Int + var max: Any private set var units: String? private set - var stepSize = 0 + var stepSize: Any = 0 + private set + var showDecimal: Boolean = false private set constructor( context: Context, nameId: Int, descriptionId: Int, - min: Int, - max: Int, + min: Any, + max: Any, units: String?, - stepSize: Int + stepSize: Any, + showDecimal: Boolean ) : super(context, nameId, descriptionId) { this.min = min this.max = max this.units = units this.stepSize = stepSize + this.showDecimal = showDecimal } constructor( name: CharSequence, description: CharSequence?, - min: Int, - max: Int, - units: String? + min: Any, + max: Any, + units: String?, + showDecimal: Boolean ) : super(name, description) { this.min = min this.max = max this.units = units + this.showDecimal = showDecimal } - abstract val selectedValue: Int + abstract val selectedValue: Any } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt index 6b4def552c..0bfcb96d6c 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt @@ -19,6 +19,7 @@ enum class MenuTag { DEBUG("debug"), GRAPHICS("graphics"), ENHANCEMENTS("enhancements"), + COLOR_CORRECTION("color_correction"), STEREOSCOPY("stereoscopy"), HACKS("hacks"), STATISTICS("statistics"), diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt index 3623f167bb..eff1b3ba13 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt @@ -51,7 +51,7 @@ class SettingsAdapter( private var settingsList: ArrayList? = null private var clickedItem: SettingsItem? = null private var clickedPosition: Int = -1 - private var seekbarProgress = 0 + private var seekbarProgress: Float = 0f private var dialog: AlertDialog? = null private var textSliderValue: TextView? = null @@ -229,21 +229,37 @@ class SettingsAdapter( fun onSliderClick(item: SliderSetting, position: Int) { clickedItem = item clickedPosition = position - seekbarProgress = item.selectedValue + seekbarProgress = when (item) { + is FloatSliderSetting -> item.selectedValue + is IntSliderSetting -> item.selectedValue.toFloat() + } val inflater = LayoutInflater.from(fragmentView.fragmentActivity) val binding = DialogSliderBinding.inflate(inflater) textSliderValue = binding.textValue - textSliderValue!!.text = seekbarProgress.toString() + textSliderValue!!.text = if (item.showDecimal) { + String.format("%.2f", seekbarProgress) + } else { + seekbarProgress.toInt().toString() + } binding.textUnits.text = item.units val slider = binding.slider - slider.valueFrom = item.min.toFloat() - slider.valueTo = item.max.toFloat() - slider.value = seekbarProgress.toFloat() - slider.stepSize = item.stepSize.toFloat() + when (item) { + is FloatSliderSetting -> { + slider.valueFrom = item.min as Float + slider.valueTo = item.max as Float + slider.stepSize = item.stepSize as Float + } + is IntSliderSetting -> { + slider.valueFrom = (item.min as Int).toFloat() + slider.valueTo = (item.max as Int).toFloat() + slider.stepSize = (item.stepSize as Int).toFloat() + } + } + slider.value = seekbarProgress slider.addOnChangeListener(this) dialog = MaterialAlertDialogBuilder(fragmentView.fragmentActivity) @@ -480,8 +496,10 @@ class SettingsAdapter( } is IntSliderSetting -> { val sliderSetting = clickedItem as IntSliderSetting - if (sliderSetting.selectedValue != seekbarProgress) fragmentView.onSettingChanged() - sliderSetting.setSelectedValue(settings, seekbarProgress) + if (sliderSetting.selectedValue != seekbarProgress.toInt()) { + fragmentView.onSettingChanged() + } + sliderSetting.setSelectedValue(settings, seekbarProgress.toInt()) closeDialog() } is FloatSliderSetting -> { @@ -489,13 +507,13 @@ class SettingsAdapter( if (sliderSetting.selectedValue != seekbarProgress) fragmentView.onSettingChanged() - sliderSetting.setSelectedValue(settings, seekbarProgress.toFloat()) + sliderSetting.setSelectedValue(settings, seekbarProgress) closeDialog() } } clickedItem = null - seekbarProgress = -1 + seekbarProgress = -1f } fun closeDialog() { @@ -510,8 +528,12 @@ class SettingsAdapter( } override fun onValueChange(slider: Slider, progress: Float, fromUser: Boolean) { - seekbarProgress = progress.toInt() - textSliderValue!!.text = seekbarProgress.toString() + seekbarProgress = progress + textSliderValue!!.text = if ((clickedItem as SliderSetting).showDecimal) { + String.format("%.2f", seekbarProgress) + } else { + seekbarProgress.toInt().toString() + } } private fun getValueForSingleChoiceSelection(item: SingleChoiceSetting, which: Int): Int { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 99b95bb7e3..15d678f752 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -34,6 +34,7 @@ import org.dolphinemu.dolphinemu.model.GpuDriverMetadata import org.dolphinemu.dolphinemu.ui.main.MainPresenter import org.dolphinemu.dolphinemu.utils.* import java.util.* +import kotlin.collections.ArrayList import kotlin.math.ceil import kotlin.math.floor @@ -119,6 +120,7 @@ class SettingsFragmentPresenter( MenuTag.GCPAD_TYPE -> addGcPadSettings(sl) MenuTag.WIIMOTE -> addWiimoteSettings(sl) MenuTag.ENHANCEMENTS -> addEnhanceSettings(sl) + MenuTag.COLOR_CORRECTION -> addColorCorrectionSettings(sl) MenuTag.STEREOSCOPY -> addStereoSettings(sl) MenuTag.HACKS -> addHackSettings(sl) MenuTag.STATISTICS -> addStatisticsSettings(sl) @@ -249,10 +251,11 @@ class SettingsFragmentPresenter( FloatSetting.MAIN_EMULATION_SPEED, R.string.speed_limit, 0, - 0, - 200, + 0f, + 200f, "%", - 1 + 1f, + false ) ) sl.add( @@ -1000,10 +1003,11 @@ class SettingsFragmentPresenter( FloatSetting.MAIN_OVERCLOCK, R.string.overclock_title, R.string.overclock_title_description, - 0, - 400, + 0f, + 400f, "%", - 1 + 1f, + false ) ) @@ -2349,9 +2353,10 @@ class SettingsFragmentPresenter( InputMappingDoubleSetting(setting), setting.getUiName(), "", - ceil(setting.getDoubleMin()).toInt(), - floor(setting.getDoubleMax()).toInt(), - setting.getUiSuffix() + ceil(setting.getDoubleMin()).toFloat(), + floor(setting.getDoubleMax()).toFloat(), + setting.getUiSuffix(), + false ) ) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt index 9009be4e7d..6dde8749c2 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt @@ -7,6 +7,7 @@ import android.text.TextUtils import android.view.View import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding +import org.dolphinemu.dolphinemu.features.settings.model.view.IntSliderSetting import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem import org.dolphinemu.dolphinemu.features.settings.model.view.SliderSetting import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter @@ -14,7 +15,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter class SliderViewHolder( private val binding: ListItemSettingBinding, adapter: SettingsAdapter?, private val context: Context -) : SettingViewHolder(binding.getRoot(), adapter!!) { +) : SettingViewHolder(binding.root, adapter!!) { private lateinit var setting: SliderSetting override val item: SettingsItem @@ -28,11 +29,25 @@ class SliderViewHolder( if (!TextUtils.isEmpty(item.description)) { binding.textSettingDescription.text = item.description } else { - binding.textSettingDescription.text = context.getString( - R.string.slider_setting_value, - setting.selectedValue, - setting.units - ) + val selectedValue: Float = if (item is IntSliderSetting) { + (setting.selectedValue as Int).toFloat() + } else { + setting.selectedValue as Float + } + + if (setting.showDecimal) { + binding.textSettingDescription.text = String.format( + context.getString(R.string.slider_setting_float_value), + selectedValue, + setting.units + ) + } else { + binding.textSettingDescription.text = String.format( + context.getString(R.string.slider_setting_value), + selectedValue, + setting.units + ) + } } setStyle(binding.textSettingName, setting) diff --git a/Source/Android/app/src/main/res/values/strings.xml b/Source/Android/app/src/main/res/values/strings.xml index 17421bb7ed..015b19bae5 100644 --- a/Source/Android/app/src/main/res/values/strings.xml +++ b/Source/Android/app/src/main/res/values/strings.xml @@ -632,7 +632,8 @@ It can efficiently compress both junk data and encrypted Wii data. Enabled Default Values - %1$d%2$s + %.0f%2$s + %.2f%2$s Disc %1$d GameCube Adapter couldn\'t be opened. Please re-plug the device. The selected GameCube controller is set to \"None\" From 75a62e116c374ff4d9def30e25b7b23e6f836a91 Mon Sep 17 00:00:00 2001 From: Dentomologist Date: Tue, 29 Aug 2023 12:07:03 -0700 Subject: [PATCH 065/120] PageFaultTest: Use GTEST_SKIP instead of early return Using GTEST_SKIP instead of just returning from the function shows that a test was skipped in the test summary. If GTEST_SKIP is called the rest of the function won't be run, just like with the return. GTEST_SKIP wasn't available until gtest 1.10, and we updated to 1.12 in 597f8f1b874bf93854ae178795c55117f680e457. --- Source/UnitTests/Core/PageFaultTest.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Source/UnitTests/Core/PageFaultTest.cpp b/Source/UnitTests/Core/PageFaultTest.cpp index 3f693923e0..fd58e1fb45 100644 --- a/Source/UnitTests/Core/PageFaultTest.cpp +++ b/Source/UnitTests/Core/PageFaultTest.cpp @@ -68,10 +68,8 @@ static void ASAN_DISABLE perform_invalid_access(void* data) TEST(PageFault, PageFault) { if (!EMM::IsExceptionHandlerSupported()) - { - // TODO: Use GTEST_SKIP() instead when GTest is updated to 1.10+ - return; - } + GTEST_SKIP() << "Skipping PageFault test because exception handler is unsupported."; + EMM::InstallExceptionHandler(); void* data = Common::AllocateMemoryPages(PAGE_GRAN); EXPECT_NE(data, nullptr); From af2c32635adb90ee89bd800cb7decc4b1a1895f8 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Mon, 31 Jul 2023 14:37:35 +0200 Subject: [PATCH 066/120] Jit: Add more error checking to ProtectStack --- Source/Core/Common/MemoryUtil.cpp | 34 ++++++++++++++++--- Source/Core/Common/MemoryUtil.h | 8 ++--- .../Core/Core/PowerPC/JitCommon/JitBase.cpp | 14 ++++++-- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/Source/Core/Common/MemoryUtil.cpp b/Source/Core/Common/MemoryUtil.cpp index b473705411..2cd393d098 100644 --- a/Source/Core/Common/MemoryUtil.cpp +++ b/Source/Core/Common/MemoryUtil.cpp @@ -160,18 +160,25 @@ void* AllocateAlignedMemory(size_t size, size_t alignment) return ptr; } -void FreeMemoryPages(void* ptr, size_t size) +bool FreeMemoryPages(void* ptr, size_t size) { if (ptr) { #ifdef _WIN32 if (!VirtualFree(ptr, 0, MEM_RELEASE)) + { PanicAlertFmt("FreeMemoryPages failed!\nVirtualFree: {}", GetLastErrorString()); + return false; + } #else if (munmap(ptr, size) != 0) + { PanicAlertFmt("FreeMemoryPages failed!\nmunmap: {}", LastStrerrorString()); + return false; + } #endif } + return true; } void FreeAlignedMemory(void* ptr) @@ -186,39 +193,56 @@ void FreeAlignedMemory(void* ptr) } } -void ReadProtectMemory(void* ptr, size_t size) +bool ReadProtectMemory(void* ptr, size_t size) { #ifdef _WIN32 DWORD oldValue; if (!VirtualProtect(ptr, size, PAGE_NOACCESS, &oldValue)) + { PanicAlertFmt("ReadProtectMemory failed!\nVirtualProtect: {}", GetLastErrorString()); + return false; + } #else if (mprotect(ptr, size, PROT_NONE) != 0) + { PanicAlertFmt("ReadProtectMemory failed!\nmprotect: {}", LastStrerrorString()); + return false; + } #endif + return true; } -void WriteProtectMemory(void* ptr, size_t size, bool allowExecute) +bool WriteProtectMemory(void* ptr, size_t size, bool allowExecute) { #ifdef _WIN32 DWORD oldValue; if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue)) + { PanicAlertFmt("WriteProtectMemory failed!\nVirtualProtect: {}", GetLastErrorString()); + return false; + } #elif !(defined(_M_ARM_64) && defined(__APPLE__)) // MacOS 11.2 on ARM does not allow for changing the access permissions of pages // that were marked executable, instead it uses the protections offered by MAP_JIT // for write protection. if (mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_EXEC) : PROT_READ) != 0) + { PanicAlertFmt("WriteProtectMemory failed!\nmprotect: {}", LastStrerrorString()); + return false; + } #endif + return true; } -void UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute) +bool UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute) { #ifdef _WIN32 DWORD oldValue; if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, &oldValue)) + { PanicAlertFmt("UnWriteProtectMemory failed!\nVirtualProtect: {}", GetLastErrorString()); + return false; + } #elif !(defined(_M_ARM_64) && defined(__APPLE__)) // MacOS 11.2 on ARM does not allow for changing the access permissions of pages // that were marked executable, instead it uses the protections offered by MAP_JIT @@ -227,8 +251,10 @@ void UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute) allowExecute ? (PROT_READ | PROT_WRITE | PROT_EXEC) : PROT_WRITE | PROT_READ) != 0) { PanicAlertFmt("UnWriteProtectMemory failed!\nmprotect: {}", LastStrerrorString()); + return false; } #endif + return true; } size_t MemPhysical() diff --git a/Source/Core/Common/MemoryUtil.h b/Source/Core/Common/MemoryUtil.h index b6489e9546..11ee1c4cbb 100644 --- a/Source/Core/Common/MemoryUtil.h +++ b/Source/Core/Common/MemoryUtil.h @@ -27,12 +27,12 @@ struct ScopedJITPageWriteAndNoExecute ~ScopedJITPageWriteAndNoExecute() { JITPageWriteDisableExecuteEnable(); } }; void* AllocateMemoryPages(size_t size); -void FreeMemoryPages(void* ptr, size_t size); +bool FreeMemoryPages(void* ptr, size_t size); void* AllocateAlignedMemory(size_t size, size_t alignment); void FreeAlignedMemory(void* ptr); -void ReadProtectMemory(void* ptr, size_t size); -void WriteProtectMemory(void* ptr, size_t size, bool executable = false); -void UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute = false); +bool ReadProtectMemory(void* ptr, size_t size); +bool WriteProtectMemory(void* ptr, size_t size, bool executable = false); +bool UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute = false); size_t MemPhysical(); } // namespace Common diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp index 7c9c707fbc..7cba8c7dfb 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp @@ -151,7 +151,12 @@ void JitBase::ProtectStack() #ifdef _WIN32 ULONG reserveSize = SAFE_STACK_SIZE; - SetThreadStackGuarantee(&reserveSize); + if (!SetThreadStackGuarantee(&reserveSize)) + { + PanicAlertFmt("Failed to set thread stack guarantee"); + m_enable_blr_optimization = false; + return; + } #else auto [stack_addr, stack_size] = Common::GetCurrentThreadStack(); @@ -184,7 +189,12 @@ void JitBase::ProtectStack() } m_stack_guard = reinterpret_cast(stack_guard_addr); - Common::ReadProtectMemory(m_stack_guard, GUARD_SIZE); + if (!Common::ReadProtectMemory(m_stack_guard, GUARD_SIZE)) + { + m_stack_guard = nullptr; + m_enable_blr_optimization = false; + return; + } #endif } From 4131dffae92c22ed938118d196ffc94da63246e1 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 30 Jul 2023 17:35:22 +0200 Subject: [PATCH 067/120] Jit: Allow BLR optimization without fastmem While both fastmem and the BLR optimization depend on fault handling, the BLR optimization doesn't depend on fastmem, and there are cases where you might want the BLR optimization but not fastmem. For me personally, it's useful when I try to use a debugger on Android and have to disable fastmem so I don't get SIGSEGVs all the time, but it would be especially useful for iOS users. --- Source/Core/Core/Core.cpp | 9 +++++---- Source/Core/Core/PowerPC/JitCommon/JitBase.cpp | 7 +++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 9ca298a837..5396c8439b 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -391,9 +391,10 @@ static void CpuThread(const std::optional& savestate_path, bool del static_cast(IDCache::GetEnvForThread()); #endif - const bool fastmem_enabled = Config::Get(Config::MAIN_FASTMEM); - if (fastmem_enabled) - EMM::InstallExceptionHandler(); // Let's run under memory watch + // The JIT need to be able to intercept faults, both for fastmem and for the BLR optimization. + const bool exception_handler = EMM::IsExceptionHandlerSupported(); + if (exception_handler) + EMM::InstallExceptionHandler(); #ifdef USE_MEMORYWATCHER s_memory_watcher = std::make_unique(); @@ -441,7 +442,7 @@ static void CpuThread(const std::optional& savestate_path, bool del s_is_started = false; - if (fastmem_enabled) + if (exception_handler) EMM::UninstallExceptionHandler(); if (GDBStub::IsActive()) diff --git a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp index 7cba8c7dfb..bcdafd9725 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitBase.cpp @@ -18,6 +18,7 @@ #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/HW/CPU.h" +#include "Core/MemTools.h" #include "Core/PowerPC/PPCAnalyst.h" #include "Core/PowerPC/PowerPC.h" #include "Core/System.h" @@ -132,7 +133,8 @@ void JitBase::RefreshConfig() analyzer.SetDivByZeroExceptionsEnabled(m_enable_div_by_zero_exceptions); bool any_watchpoints = m_system.GetPowerPC().GetMemChecks().HasAny(); - jo.fastmem = m_fastmem_enabled && jo.fastmem_arena && (m_ppc_state.msr.DR || !any_watchpoints); + jo.fastmem = m_fastmem_enabled && jo.fastmem_arena && (m_ppc_state.msr.DR || !any_watchpoints) && + EMM::IsExceptionHandlerSupported(); jo.memcheck = m_system.IsMMUMode() || m_system.IsPauseOnPanicMode() || any_watchpoints; jo.fp_exceptions = m_enable_float_exceptions; jo.div_by_zero_exceptions = m_enable_div_by_zero_exceptions; @@ -140,7 +142,8 @@ void JitBase::RefreshConfig() void JitBase::InitBLROptimization() { - m_enable_blr_optimization = jo.enableBlocklink && m_fastmem_enabled && !m_enable_debugging; + m_enable_blr_optimization = + jo.enableBlocklink && !m_enable_debugging && EMM::IsExceptionHandlerSupported(); m_cleanup_after_stackfault = false; } From 5bd7756064e4759fe13a557106cf9629908a04f0 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 2 Sep 2023 04:01:32 +0200 Subject: [PATCH 068/120] Common/MemArena: Add LazyMemoryRegion to represent a zero-initialized memory region whose pages are only allocated on first access. --- Source/Core/Common/MemArena.h | 38 +++++++++++++++++++++++ Source/Core/Common/MemArenaAndroid.cpp | 43 ++++++++++++++++++++++++++ Source/Core/Common/MemArenaUnix.cpp | 43 ++++++++++++++++++++++++++ Source/Core/Common/MemArenaWin.cpp | 43 ++++++++++++++++++++++++++ 4 files changed, 167 insertions(+) diff --git a/Source/Core/Common/MemArena.h b/Source/Core/Common/MemArena.h index adb944acf9..33ebb66eb4 100644 --- a/Source/Core/Common/MemArena.h +++ b/Source/Core/Common/MemArena.h @@ -122,4 +122,42 @@ private: #endif }; +// This class represents a single fixed-size memory region where the individual memory pages are +// only actually allocated on first access. The memory will be zero on first access. +class LazyMemoryRegion final +{ +public: + LazyMemoryRegion(); + ~LazyMemoryRegion(); + LazyMemoryRegion(const LazyMemoryRegion&) = delete; + LazyMemoryRegion(LazyMemoryRegion&&) = delete; + LazyMemoryRegion& operator=(const LazyMemoryRegion&) = delete; + LazyMemoryRegion& operator=(LazyMemoryRegion&&) = delete; + + /// + /// Reserve a memory region. + /// + /// @param size The size of the region. + /// + /// @return The address the region was mapped at. Returns nullptr on failure. + /// + void* Create(size_t size); + + /// + /// Reset the memory region back to zero, throwing away any mapped pages. + /// This can only be called after a successful call to Create(). + /// + void Clear(); + + /// + /// Release the memory previously reserved with Create(). After this call the pointer that was + /// returned by Create() will become invalid. + /// + void Release(); + +private: + void* m_memory = nullptr; + size_t m_size = 0; +}; + } // namespace Common diff --git a/Source/Core/Common/MemArenaAndroid.cpp b/Source/Core/Common/MemArenaAndroid.cpp index 01b210100a..4a9e2f68b4 100644 --- a/Source/Core/Common/MemArenaAndroid.cpp +++ b/Source/Core/Common/MemArenaAndroid.cpp @@ -19,6 +19,7 @@ #include #include +#include "Common/Assert.h" #include "Common/CommonFuncs.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" @@ -142,4 +143,46 @@ void MemArena::UnmapFromMemoryRegion(void* view, size_t size) if (retval == MAP_FAILED) NOTICE_LOG_FMT(MEMMAP, "mmap failed"); } + +LazyMemoryRegion::LazyMemoryRegion() = default; + +LazyMemoryRegion::~LazyMemoryRegion() +{ + Release(); +} + +void* LazyMemoryRegion::Create(size_t size) +{ + ASSERT(!m_memory); + + void* memory = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (!memory) + { + NOTICE_LOG_FMT(MEMMAP, "Memory allocation of {} bytes failed.", size); + return nullptr; + } + + m_memory = memory; + m_size = size; + + return memory; +} + +void LazyMemoryRegion::Clear() +{ + ASSERT(m_memory); + + mmap(m_memory, m_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); +} + +void LazyMemoryRegion::Release() +{ + if (m_memory) + { + munmap(m_memory, m_size); + m_memory = nullptr; + m_size = 0; + } +} + } // namespace Common diff --git a/Source/Core/Common/MemArenaUnix.cpp b/Source/Core/Common/MemArenaUnix.cpp index 1532699276..452c2c50c8 100644 --- a/Source/Core/Common/MemArenaUnix.cpp +++ b/Source/Core/Common/MemArenaUnix.cpp @@ -16,6 +16,7 @@ #include #include +#include "Common/Assert.h" #include "Common/CommonFuncs.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" @@ -108,4 +109,46 @@ void MemArena::UnmapFromMemoryRegion(void* view, size_t size) if (retval == MAP_FAILED) NOTICE_LOG_FMT(MEMMAP, "mmap failed"); } + +LazyMemoryRegion::LazyMemoryRegion() = default; + +LazyMemoryRegion::~LazyMemoryRegion() +{ + Release(); +} + +void* LazyMemoryRegion::Create(size_t size) +{ + ASSERT(!m_memory); + + void* memory = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (!memory) + { + NOTICE_LOG_FMT(MEMMAP, "Memory allocation of {} bytes failed.", size); + return nullptr; + } + + m_memory = memory; + m_size = size; + + return memory; +} + +void LazyMemoryRegion::Clear() +{ + ASSERT(m_memory); + + mmap(m_memory, m_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); +} + +void LazyMemoryRegion::Release() +{ + if (m_memory) + { + munmap(m_memory, m_size); + m_memory = nullptr; + m_size = 0; + } +} + } // namespace Common diff --git a/Source/Core/Common/MemArenaWin.cpp b/Source/Core/Common/MemArenaWin.cpp index 9d4a88d54a..ebf078f45b 100644 --- a/Source/Core/Common/MemArenaWin.cpp +++ b/Source/Core/Common/MemArenaWin.cpp @@ -433,4 +433,47 @@ void MemArena::UnmapFromMemoryRegion(void* view, size_t size) UnmapViewOfFile(view); } + +LazyMemoryRegion::LazyMemoryRegion() = default; + +LazyMemoryRegion::~LazyMemoryRegion() +{ + Release(); +} + +void* LazyMemoryRegion::Create(size_t size) +{ + ASSERT(!m_memory); + + void* memory = VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (!memory) + { + NOTICE_LOG_FMT(MEMMAP, "Memory allocation of {} bytes failed.", size); + return nullptr; + } + + m_memory = memory; + m_size = size; + + return memory; +} + +void LazyMemoryRegion::Clear() +{ + ASSERT(m_memory); + + VirtualFree(m_memory, m_size, MEM_DECOMMIT); + VirtualAlloc(m_memory, m_size, MEM_COMMIT, PAGE_READWRITE); +} + +void LazyMemoryRegion::Release() +{ + if (m_memory) + { + VirtualFree(m_memory, 0, MEM_RELEASE); + m_memory = nullptr; + m_size = 0; + } +} + } // namespace Common From f1c1c6ded690b34c8e5add75fecc96fff00848e5 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 2 Sep 2023 04:03:15 +0200 Subject: [PATCH 069/120] JitCache: Fix potentially dangling pointer to fast block map. Whenever JitBaseBlockCache::Clear() got called, it threw away the memory mapping for the fast block map and created a new one. This new mapping typically got mapped at the same address at the old one, but this is not guaranteed. The pointer to the mapping gets embedded in the generated dispatcher code in Jit64AsmRoutineManager::Generate(), which is only called once on game boot, so if the new mapping ended up at a different address than the old one, the pointer in the ASM pointed at garbage, leading to a crash. This fixes the issue by guaranteeing that the new mapping is mapped at the same address. --- .../Core/Core/PowerPC/JitCommon/JitCache.cpp | 31 +++++-------------- Source/Core/Core/PowerPC/JitCommon/JitCache.h | 2 +- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp index a41ebb71b2..98ce48f24a 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp @@ -42,7 +42,11 @@ void JitBaseBlockCache::Init() { Common::JitRegister::Init(Config::Get(Config::MAIN_PERF_MAP_DIR)); - m_block_map_arena.GrabSHMSegment(FAST_BLOCK_MAP_SIZE, "dolphin-emu-jitblock"); + m_fast_block_map = reinterpret_cast(m_block_map_arena.Create(FAST_BLOCK_MAP_SIZE)); + if (m_fast_block_map) + m_fast_block_map_ptr = m_fast_block_map; + else + m_fast_block_map_ptr = m_fast_block_map_fallback.data(); Clear(); } @@ -51,12 +55,7 @@ void JitBaseBlockCache::Shutdown() { Common::JitRegister::Shutdown(); - if (m_fast_block_map) - { - m_block_map_arena.ReleaseView(m_fast_block_map, FAST_BLOCK_MAP_SIZE); - } - - m_block_map_arena.ReleaseSHMSegment(); + m_block_map_arena.Release(); } // This clears the JIT cache. It's called from JitCache.cpp when the JIT cache @@ -80,23 +79,7 @@ void JitBaseBlockCache::Clear() valid_block.ClearAll(); if (m_fast_block_map) - { - m_block_map_arena.ReleaseView(m_fast_block_map, FAST_BLOCK_MAP_SIZE); - m_block_map_arena.ReleaseSHMSegment(); - m_block_map_arena.GrabSHMSegment(FAST_BLOCK_MAP_SIZE, "dolphin-emu-jitblock"); - } - - m_fast_block_map = - reinterpret_cast(m_block_map_arena.CreateView(0, FAST_BLOCK_MAP_SIZE)); - - if (m_fast_block_map) - { - m_fast_block_map_ptr = m_fast_block_map; - } - else - { - m_fast_block_map_ptr = m_fast_block_map_fallback.data(); - } + m_block_map_arena.Clear(); } void JitBaseBlockCache::Reset() diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.h b/Source/Core/Core/PowerPC/JitCommon/JitCache.h index d3f353e815..cf6f785d98 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.h @@ -212,7 +212,7 @@ private: // This is used as a fast cache of block_map used in the assembly dispatcher. // It is implemented via a shm segment using m_block_map_arena. JitBlock** m_fast_block_map = 0; - Common::MemArena m_block_map_arena; + Common::LazyMemoryRegion m_block_map_arena; // An alternative for the above fast_block_map but without a shm segment // in case the shm memory region couldn't be allocated. From d5d3eb5025570e3e62e45d6985f9e34c356ceadd Mon Sep 17 00:00:00 2001 From: Dentomologist Date: Fri, 1 Sep 2023 22:50:48 -0700 Subject: [PATCH 070/120] TextureCacheBase: Add m_ prefix to member variables --- Source/Core/VideoCommon/TextureCacheBase.cpp | 206 +++++++++---------- Source/Core/VideoCommon/TextureCacheBase.h | 28 +-- 2 files changed, 117 insertions(+), 117 deletions(-) diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index 37dadba967..fddd8f7bee 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -81,23 +81,23 @@ TCacheEntry::~TCacheEntry() void TextureCacheBase::CheckTempSize(size_t required_size) { - if (required_size <= temp_size) + if (required_size <= m_temp_size) return; - temp_size = required_size; - Common::FreeAlignedMemory(temp); - temp = static_cast(Common::AllocateAlignedMemory(temp_size, 16)); + m_temp_size = required_size; + Common::FreeAlignedMemory(m_temp); + m_temp = static_cast(Common::AllocateAlignedMemory(m_temp_size, 16)); } TextureCacheBase::TextureCacheBase() { SetBackupConfig(g_ActiveConfig); - temp_size = 2048 * 2048 * 4; - temp = static_cast(Common::AllocateAlignedMemory(temp_size, 16)); + m_temp_size = 2048 * 2048 * 4; + m_temp = static_cast(Common::AllocateAlignedMemory(m_temp_size, 16)); - TexDecoder_SetTexFmtOverlayOptions(backup_config.texfmt_overlay, - backup_config.texfmt_overlay_center); + TexDecoder_SetTexFmtOverlayOptions(m_backup_config.texfmt_overlay, + m_backup_config.texfmt_overlay_center); HiresTexture::Init(); @@ -117,8 +117,8 @@ void TextureCacheBase::Shutdown() TextureCacheBase::~TextureCacheBase() { - Common::FreeAlignedMemory(temp); - temp = nullptr; + Common::FreeAlignedMemory(m_temp); + m_temp = nullptr; } bool TextureCacheBase::Initialize() @@ -137,18 +137,18 @@ void TextureCacheBase::Invalidate() FlushEFBCopies(); TMEM::InvalidateAll(); - for (auto& bind : bound_textures) + for (auto& bind : m_bound_textures) bind.reset(); - textures_by_hash.clear(); - textures_by_address.clear(); + m_textures_by_hash.clear(); + m_textures_by_address.clear(); - texture_pool.clear(); + m_texture_pool.clear(); } void TextureCacheBase::OnConfigChanged(const VideoConfig& config) { - if (config.bHiresTextures != backup_config.hires_textures || - config.bCacheHiresTextures != backup_config.cache_hires_textures) + if (config.bHiresTextures != m_backup_config.hires_textures || + config.bCacheHiresTextures != m_backup_config.cache_hires_textures) { HiresTexture::Update(); } @@ -157,15 +157,15 @@ void TextureCacheBase::OnConfigChanged(const VideoConfig& config) config.graphics_mod_config ? config.graphics_mod_config->GetChangeCount() : 0; // TODO: Invalidating texcache is really stupid in some of these cases - if (config.iSafeTextureCache_ColorSamples != backup_config.color_samples || - config.bTexFmtOverlayEnable != backup_config.texfmt_overlay || - config.bTexFmtOverlayCenter != backup_config.texfmt_overlay_center || - config.bHiresTextures != backup_config.hires_textures || - config.bEnableGPUTextureDecoding != backup_config.gpu_texture_decoding || - config.bDisableCopyToVRAM != backup_config.disable_vram_copies || - config.bArbitraryMipmapDetection != backup_config.arbitrary_mipmap_detection || - config.bGraphicMods != backup_config.graphics_mods || - change_count != backup_config.graphics_mod_change_count) + if (config.iSafeTextureCache_ColorSamples != m_backup_config.color_samples || + config.bTexFmtOverlayEnable != m_backup_config.texfmt_overlay || + config.bTexFmtOverlayCenter != m_backup_config.texfmt_overlay_center || + config.bHiresTextures != m_backup_config.hires_textures || + config.bEnableGPUTextureDecoding != m_backup_config.gpu_texture_decoding || + config.bDisableCopyToVRAM != m_backup_config.disable_vram_copies || + config.bArbitraryMipmapDetection != m_backup_config.arbitrary_mipmap_detection || + config.bGraphicMods != m_backup_config.graphics_mods || + change_count != m_backup_config.graphics_mod_change_count) { Invalidate(); TexDecoder_SetTexFmtOverlayOptions(config.bTexFmtOverlayEnable, config.bTexFmtOverlayCenter); @@ -176,8 +176,8 @@ void TextureCacheBase::OnConfigChanged(const VideoConfig& config) void TextureCacheBase::Cleanup(int _frameCount) { - TexAddrCache::iterator iter = textures_by_address.begin(); - TexAddrCache::iterator tcend = textures_by_address.end(); + TexAddrCache::iterator iter = m_textures_by_address.begin(); + TexAddrCache::iterator tcend = m_textures_by_address.end(); while (iter != tcend) { if (iter->second->frameCount == FRAMECOUNT_INVALID) @@ -214,8 +214,8 @@ void TextureCacheBase::Cleanup(int _frameCount) } } - TexPool::iterator iter2 = texture_pool.begin(); - TexPool::iterator tcend2 = texture_pool.end(); + TexPool::iterator iter2 = m_texture_pool.begin(); + TexPool::iterator tcend2 = m_texture_pool.end(); while (iter2 != tcend2) { if (iter2->second.frameCount == FRAMECOUNT_INVALID) @@ -224,7 +224,7 @@ void TextureCacheBase::Cleanup(int _frameCount) } if (_frameCount > TEXTURE_POOL_KILL_THRESHOLD + iter2->second.frameCount) { - iter2 = texture_pool.erase(iter2); + iter2 = m_texture_pool.erase(iter2); } else { @@ -246,18 +246,18 @@ bool TCacheEntry::OverlapsMemoryRange(u32 range_address, u32 range_size) const void TextureCacheBase::SetBackupConfig(const VideoConfig& config) { - backup_config.color_samples = config.iSafeTextureCache_ColorSamples; - backup_config.texfmt_overlay = config.bTexFmtOverlayEnable; - backup_config.texfmt_overlay_center = config.bTexFmtOverlayCenter; - backup_config.hires_textures = config.bHiresTextures; - backup_config.cache_hires_textures = config.bCacheHiresTextures; - backup_config.stereo_3d = config.stereo_mode != StereoMode::Off; - backup_config.efb_mono_depth = config.bStereoEFBMonoDepth; - backup_config.gpu_texture_decoding = config.bEnableGPUTextureDecoding; - backup_config.disable_vram_copies = config.bDisableCopyToVRAM; - backup_config.arbitrary_mipmap_detection = config.bArbitraryMipmapDetection; - backup_config.graphics_mods = config.bGraphicMods; - backup_config.graphics_mod_change_count = + m_backup_config.color_samples = config.iSafeTextureCache_ColorSamples; + m_backup_config.texfmt_overlay = config.bTexFmtOverlayEnable; + m_backup_config.texfmt_overlay_center = config.bTexFmtOverlayCenter; + m_backup_config.hires_textures = config.bHiresTextures; + m_backup_config.cache_hires_textures = config.bCacheHiresTextures; + m_backup_config.stereo_3d = config.stereo_mode != StereoMode::Off; + m_backup_config.efb_mono_depth = config.bStereoEFBMonoDepth; + m_backup_config.gpu_texture_decoding = config.bEnableGPUTextureDecoding; + m_backup_config.disable_vram_copies = config.bDisableCopyToVRAM; + m_backup_config.arbitrary_mipmap_detection = config.bArbitraryMipmapDetection; + m_backup_config.graphics_mods = config.bGraphicMods; + m_backup_config.graphics_mod_change_count = config.graphics_mod_config ? config.graphics_mod_config->GetChangeCount() : 0; } @@ -348,7 +348,7 @@ RcTcacheEntry TextureCacheBase::ApplyPaletteToEntry(RcTcacheEntry& entry, const g_gfx->EndUtilityDrawing(); } - textures_by_address.emplace(decoded_entry->addr, decoded_entry); + m_textures_by_address.emplace(decoded_entry->addr, decoded_entry); return decoded_entry; } @@ -394,7 +394,7 @@ RcTcacheEntry TextureCacheBase::ReinterpretEntry(const RcTcacheEntry& existing_e g_gfx->EndUtilityDrawing(); reinterpreted_entry->texture->FinishedRendering(); - textures_by_address.emplace(reinterpreted_entry->addr, reinterpreted_entry); + m_textures_by_address.emplace(reinterpreted_entry->addr, reinterpreted_entry); return reinterpreted_entry; } @@ -431,7 +431,7 @@ void TextureCacheBase::ScaleTextureCacheEntryTo(RcTcacheEntry& entry, u32 new_wi // At this point new_texture has the old texture in it, // we can potentially reuse this, so let's move it back to the pool auto config = new_texture->texture->GetConfig(); - texture_pool.emplace( + m_texture_pool.emplace( config, TexPoolEntry(std::move(new_texture->texture), std::move(new_texture->framebuffer))); } @@ -562,7 +562,7 @@ void TextureCacheBase::DoState(PointerWrap& p) // Flush all pending XFB copies before either loading or saving. FlushEFBCopies(); - p.Do(last_entry_id); + p.Do(m_last_entry_id); if (p.IsWriteMode() || p.IsMeasureMode()) DoSaveState(p); @@ -601,14 +601,14 @@ void TextureCacheBase::DoSaveState(PointerWrap& p) return iter != entry_map.end() ? std::make_optional(iter->second) : std::nullopt; }; - // Transform the textures_by_address and textures_by_hash maps to a mapping + // Transform the m_textures_by_address and m_textures_by_hash maps to a mapping // of address/hash to entry ID. std::vector> textures_by_address_list; std::vector> textures_by_hash_list; std::vector> bound_textures_list; if (Config::Get(Config::GFX_SAVE_TEXTURE_CACHE_TO_STATE)) { - for (const auto& it : textures_by_address) + for (const auto& it : m_textures_by_address) { if (ShouldSaveEntry(it.second)) { @@ -616,7 +616,7 @@ void TextureCacheBase::DoSaveState(PointerWrap& p) textures_by_address_list.emplace_back(it.first, id); } } - for (const auto& it : textures_by_hash) + for (const auto& it : m_textures_by_hash) { if (ShouldSaveEntry(it.second)) { @@ -624,10 +624,10 @@ void TextureCacheBase::DoSaveState(PointerWrap& p) textures_by_hash_list.emplace_back(it.first, id); } } - for (u32 i = 0; i < bound_textures.size(); i++) + for (u32 i = 0; i < m_bound_textures.size(); i++) { - const auto& tentry = bound_textures[i]; - if (bound_textures[i] && ShouldSaveEntry(tentry)) + const auto& tentry = m_bound_textures[i]; + if (m_bound_textures[i] && ShouldSaveEntry(tentry)) { const u32 id = AddCacheEntryToMap(tentry); bound_textures_list.emplace_back(i, id); @@ -715,7 +715,7 @@ void TextureCacheBase::DoLoadState(PointerWrap& p) auto tex = DeserializeTexture(p); auto entry = std::make_shared(std::move(tex->texture), std::move(tex->framebuffer)); - entry->textures_by_hash_iter = textures_by_hash.end(); + entry->textures_by_hash_iter = m_textures_by_hash.end(); entry->DoState(p); if (entry->texture && commit_state) id_map.emplace(i, entry); @@ -746,7 +746,7 @@ void TextureCacheBase::DoLoadState(PointerWrap& p) auto& entry = GetEntry(id); if (entry) - textures_by_address.emplace(addr, entry); + m_textures_by_address.emplace(addr, entry); } // Fill in hash map. @@ -760,12 +760,12 @@ void TextureCacheBase::DoLoadState(PointerWrap& p) auto& entry = GetEntry(id); if (entry) - entry->textures_by_hash_iter = textures_by_hash.emplace(hash, entry); + entry->textures_by_hash_iter = m_textures_by_hash.emplace(hash, entry); } // Clear bound textures - for (u32 i = 0; i < bound_textures.size(); i++) - bound_textures[i].reset(); + for (u32 i = 0; i < m_bound_textures.size(); i++) + m_bound_textures[i].reset(); // Fill in bound textures p.Do(size); @@ -778,7 +778,7 @@ void TextureCacheBase::DoLoadState(PointerWrap& p) auto& entry = GetEntry(id); if (entry) - bound_textures[index] = entry; + m_bound_textures[index] = entry; } } @@ -1114,9 +1114,9 @@ void TextureCacheBase::BindTextures(BitSet32 used_textures) { auto& system = Core::System::GetInstance(); auto& pixel_shader_manager = system.GetPixelShaderManager(); - for (u32 i = 0; i < bound_textures.size(); i++) + for (u32 i = 0; i < m_bound_textures.size(); i++) { - const RcTcacheEntry& tentry = bound_textures[i]; + const RcTcacheEntry& tentry = m_bound_textures[i]; if (used_textures[i] && tentry) { g_gfx->SetTexture(i, tentry->texture.get()); @@ -1295,9 +1295,9 @@ TCacheEntry* TextureCacheBase::LoadImpl(const TextureInfo& texture_info, bool fo { // if this stage was not invalidated by changes to texture registers, keep the current texture if (!force_reload && TMEM::IsValid(texture_info.GetStage()) && - bound_textures[texture_info.GetStage()]) + m_bound_textures[texture_info.GetStage()]) { - TCacheEntry* entry = bound_textures[texture_info.GetStage()].get(); + TCacheEntry* entry = m_bound_textures[texture_info.GetStage()].get(); // If the TMEM configuration is such that this texture is more or less guaranteed to still // be in TMEM, then we know we can reuse the old entry without even hashing the memory // @@ -1337,7 +1337,7 @@ TCacheEntry* TextureCacheBase::LoadImpl(const TextureInfo& texture_info, bool fo action->OnTextureLoad(&texture_load); } } - bound_textures[texture_info.GetStage()] = entry; + m_bound_textures[texture_info.GetStage()] = entry; // We need to keep track of invalided textures until they have actually been replaced or // re-loaded @@ -1427,12 +1427,12 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp // // For efb copies, the entry created in CopyRenderTargetToTexture always has to be used, or else // it was done in vain. - auto iter_range = textures_by_address.equal_range(texture_info.GetRawAddress()); + auto iter_range = m_textures_by_address.equal_range(texture_info.GetRawAddress()); TexAddrCache::iterator iter = iter_range.first; TexAddrCache::iterator oldest_entry = iter; int temp_frameCount = 0x7fffffff; - TexAddrCache::iterator unconverted_copy = textures_by_address.end(); - TexAddrCache::iterator unreinterpreted_copy = textures_by_address.end(); + TexAddrCache::iterator unconverted_copy = m_textures_by_address.end(); + TexAddrCache::iterator unreinterpreted_copy = m_textures_by_address.end(); while (iter != iter_range.second) { @@ -1482,7 +1482,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp else { // Prefer the already-converted copy. - unconverted_copy = textures_by_address.end(); + unconverted_copy = m_textures_by_address.end(); } // TODO: We should check width/height/levels for EFB copies. I'm not sure what effect @@ -1540,7 +1540,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp ++iter; } - if (unreinterpreted_copy != textures_by_address.end()) + if (unreinterpreted_copy != m_textures_by_address.end()) { auto decoded_entry = ReinterpretEntry(unreinterpreted_copy->second, texture_info.GetTextureFormat()); @@ -1554,7 +1554,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp return decoded_entry; } - if (unconverted_copy != textures_by_address.end()) + if (unconverted_copy != m_textures_by_address.end()) { auto decoded_entry = ApplyPaletteToEntry( unconverted_copy->second, texture_info.GetTlutAddress(), texture_info.GetTlutFormat()); @@ -1575,7 +1575,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp std::max(texture_info.GetTextureSize(), palette_size) <= (u32)textureCacheSafetyColorSampleSize * 8) { - auto hash_range = textures_by_hash.equal_range(full_hash); + auto hash_range = m_textures_by_hash.equal_range(full_hash); TexHashCache::iterator hash_iter = hash_range.first; while (hash_iter != hash_range.second) { @@ -1777,7 +1777,7 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry( total_texture_size += mip_downsample_buffer_size; CheckTempSize(total_texture_size); - dst_buffer = temp; + dst_buffer = m_temp; if (!(texture_info.GetTextureFormat() == TextureFormat::RGBA8 && texture_info.IsFromTmem())) { TexDecoder_Decode(dst_buffer, texture_info.GetData(), expanded_width, expanded_height, @@ -1841,12 +1841,12 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry( } } - const auto iter = textures_by_address.emplace(texture_info.GetRawAddress(), entry); + const auto iter = m_textures_by_address.emplace(texture_info.GetRawAddress(), entry); if (safety_color_sample_size == 0 || std::max(texture_info.GetTextureSize(), creation_info.palette_size) <= (u32)safety_color_sample_size * 8) { - entry->textures_by_hash_iter = textures_by_hash.emplace(creation_info.full_hash, entry); + entry->textures_by_hash_iter = m_textures_by_hash.emplace(creation_info.full_hash, entry); } const TextureAndTLUTFormat full_format(texture_info.GetTextureFormat(), @@ -1860,7 +1860,7 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry( entry->SetNotCopy(); INCSTAT(g_stats.num_textures_uploaded); - SETSTAT(g_stats.num_textures_alive, static_cast(textures_by_address.size())); + SETSTAT(g_stats.num_textures_alive, static_cast(m_textures_by_address.size())); entry = DoPartialTextureUpdates(iter->second, texture_info.GetTlutAddress(), texture_info.GetTlutFormat()); @@ -1930,8 +1930,8 @@ RcTcacheEntry TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height { const u32 decoded_size = width * height * sizeof(u32); CheckTempSize(decoded_size); - TexDecoder_DecodeXFB(temp, src_data, width, height, stride); - entry->texture->Load(0, width, height, width, temp, decoded_size); + TexDecoder_DecodeXFB(m_temp, src_data, width, height, stride); + entry->texture->Load(0, width, height, width, m_temp, decoded_size); } // Stitch any VRAM copies into the new RAM copy. @@ -1939,8 +1939,8 @@ RcTcacheEntry TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height entry->texture->FinishedRendering(); // Insert into the texture cache so we can re-use it next frame, if needed. - textures_by_address.emplace(entry->addr, entry); - SETSTAT(g_stats.num_textures_alive, static_cast(textures_by_address.size())); + m_textures_by_address.emplace(entry->addr, entry); + SETSTAT(g_stats.num_textures_alive, static_cast(m_textures_by_address.size())); INCSTAT(g_stats.num_textures_uploaded); if (g_ActiveConfig.bDumpXFBTarget || g_ActiveConfig.bGraphicMods) @@ -1965,7 +1965,7 @@ RcTcacheEntry TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height RcTcacheEntry TextureCacheBase::GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride) { - auto iter_range = textures_by_address.equal_range(address); + auto iter_range = m_textures_by_address.equal_range(address); TexAddrCache::iterator iter = iter_range.first; while (iter != iter_range.second) @@ -2526,10 +2526,10 @@ void TextureCacheBase::CopyRenderTargetToTexture( // Do not load textures by hash, if they were at least partly overwritten by an efb copy. // In this case, comparing the hash is not enough to check, if two textures are identical. - if (overlapping_entry->textures_by_hash_iter != textures_by_hash.end()) + if (overlapping_entry->textures_by_hash_iter != m_textures_by_hash.end()) { - textures_by_hash.erase(overlapping_entry->textures_by_hash_iter); - overlapping_entry->textures_by_hash_iter = textures_by_hash.end(); + m_textures_by_hash.erase(overlapping_entry->textures_by_hash_iter); + overlapping_entry->textures_by_hash_iter = m_textures_by_hash.end(); } } ++iter.first; @@ -2553,7 +2553,7 @@ void TextureCacheBase::CopyRenderTargetToTexture( { const u64 hash = entry->CalculateHash(); entry->SetHashes(hash, hash); - textures_by_address.emplace(dstAddr, std::move(entry)); + m_textures_by_address.emplace(dstAddr, std::move(entry)); } } @@ -2569,10 +2569,10 @@ void TextureCacheBase::FlushEFBCopies() void TextureCacheBase::FlushStaleBinds() { - for (u32 i = 0; i < bound_textures.size(); i++) + for (u32 i = 0; i < m_bound_textures.size(); i++) { if (!TMEM::IsCached(i)) - bound_textures[i].reset(); + m_bound_textures[i].reset(); } } @@ -2709,8 +2709,8 @@ RcTcacheEntry TextureCacheBase::AllocateCacheEntry(const TextureConfig& config) auto cacheEntry = std::make_shared(std::move(alloc->texture), std::move(alloc->framebuffer)); - cacheEntry->textures_by_hash_iter = textures_by_hash.end(); - cacheEntry->id = last_entry_id++; + cacheEntry->textures_by_hash_iter = m_textures_by_hash.end(); + cacheEntry->id = m_last_entry_id++; return cacheEntry; } @@ -2718,10 +2718,10 @@ std::optional TextureCacheBase::AllocateTexture(const TextureConfig& config) { TexPool::iterator iter = FindMatchingTextureFromPool(config); - if (iter != texture_pool.end()) + if (iter != m_texture_pool.end()) { auto entry = std::move(iter->second); - texture_pool.erase(iter); + m_texture_pool.erase(iter); return std::move(entry); } @@ -2757,16 +2757,16 @@ TextureCacheBase::FindMatchingTextureFromPool(const TextureConfig& config) // which potentially means that a driver has to maintain two copies of the texture anyway. // Render-target textures are fine through, as they have to be generated in a seperated pass. // As non-render-target textures are usually static, this should not matter much. - auto range = texture_pool.equal_range(config); + auto range = m_texture_pool.equal_range(config); auto matching_iter = std::find_if(range.first, range.second, [](const auto& iter) { return iter.first.IsRenderTarget() || iter.second.frameCount != FRAMECOUNT_INVALID; }); - return matching_iter != range.second ? matching_iter : texture_pool.end(); + return matching_iter != range.second ? matching_iter : m_texture_pool.end(); } TextureCacheBase::TexAddrCache::iterator TextureCacheBase::GetTexCacheIter(TCacheEntry* entry) { - auto iter_range = textures_by_address.equal_range(entry->addr); + auto iter_range = m_textures_by_address.equal_range(entry->addr); TexAddrCache::iterator iter = iter_range.first; while (iter != iter_range.second) { @@ -2776,7 +2776,7 @@ TextureCacheBase::TexAddrCache::iterator TextureCacheBase::GetTexCacheIter(TCach } ++iter; } - return textures_by_address.end(); + return m_textures_by_address.end(); } std::pair @@ -2790,8 +2790,8 @@ TextureCacheBase::FindOverlappingTextures(u32 addr, u32 size_in_bytes) // 1024 x 1024 texel times 8 nibbles per texel constexpr u32 max_texture_size = 1024 * 1024 * 4; u32 lower_addr = addr > max_texture_size ? addr - max_texture_size : 0; - auto begin = textures_by_address.lower_bound(lower_addr); - auto end = textures_by_address.upper_bound(addr + size_in_bytes); + auto begin = m_textures_by_address.lower_bound(lower_addr); + auto end = m_textures_by_address.upper_bound(addr + size_in_bytes); return std::make_pair(begin, end); } @@ -2799,15 +2799,15 @@ TextureCacheBase::FindOverlappingTextures(u32 addr, u32 size_in_bytes) TextureCacheBase::TexAddrCache::iterator TextureCacheBase::InvalidateTexture(TexAddrCache::iterator iter, bool discard_pending_efb_copy) { - if (iter == textures_by_address.end()) - return textures_by_address.end(); + if (iter == m_textures_by_address.end()) + return m_textures_by_address.end(); RcTcacheEntry& entry = iter->second; - if (entry->textures_by_hash_iter != textures_by_hash.end()) + if (entry->textures_by_hash_iter != m_textures_by_hash.end()) { - textures_by_hash.erase(entry->textures_by_hash_iter); - entry->textures_by_hash_iter = textures_by_hash.end(); + m_textures_by_hash.erase(entry->textures_by_hash_iter); + entry->textures_by_hash_iter = m_textures_by_hash.end(); } // If this is a pending EFB copy, we don't want to flush it here. @@ -2840,7 +2840,7 @@ TextureCacheBase::InvalidateTexture(TexAddrCache::iterator iter, bool discard_pe } entry->invalidated = true; - return textures_by_address.erase(iter); + return m_textures_by_address.erase(iter); } void TextureCacheBase::ReleaseToPool(TCacheEntry* entry) @@ -2848,8 +2848,8 @@ void TextureCacheBase::ReleaseToPool(TCacheEntry* entry) if (!entry->texture) return; auto config = entry->texture->GetConfig(); - texture_pool.emplace(config, - TexPoolEntry(std::move(entry->texture), std::move(entry->framebuffer))); + m_texture_pool.emplace(config, + TexPoolEntry(std::move(entry->texture), std::move(entry->framebuffer))); } bool TextureCacheBase::CreateUtilityTextures() diff --git a/Source/Core/VideoCommon/TextureCacheBase.h b/Source/Core/VideoCommon/TextureCacheBase.h index 3592535a65..3c942ed8c2 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.h +++ b/Source/Core/VideoCommon/TextureCacheBase.h @@ -138,7 +138,7 @@ struct TCacheEntry u64 id = 0; u32 content_semaphore = 0; // Counts up - // Indicates that this TCacheEntry has been invalided from textures_by_address + // Indicates that this TCacheEntry has been invalided from m_textures_by_address bool invalidated = false; bool reference_changed = false; // used by xfb to determine when a reference xfb changed @@ -151,7 +151,7 @@ struct TCacheEntry // used to delete textures which haven't been used for TEXTURE_KILL_THRESHOLD frames int frameCount = FRAMECOUNT_INVALID; - // Keep an iterator to the entry in textures_by_hash, so it does not need to be searched when + // Keep an iterator to the entry in m_textures_by_hash, so it does not need to be searched when // removing the cache entry std::multimap>::iterator textures_by_hash_iter; @@ -330,8 +330,8 @@ protected: float gamma, bool clamp_top, bool clamp_bottom, const std::array& filter_coefficients); - alignas(16) u8* temp = nullptr; - size_t temp_size = 0; + alignas(16) u8* m_temp = nullptr; + size_t m_temp_size = 0; private: using TexAddrCache = std::multimap; @@ -405,20 +405,20 @@ private: void DoSaveState(PointerWrap& p); void DoLoadState(PointerWrap& p); - // textures_by_address is the authoritive version of what's actually "in" the texture cache + // m_textures_by_address is the authoritive version of what's actually "in" the texture cache // but it's possible for invalidated TCache entries to live on elsewhere - TexAddrCache textures_by_address; + TexAddrCache m_textures_by_address; - // textures_by_hash is an alternative view of the texture cache - // All textures in here will also be in textures_by_address - TexHashCache textures_by_hash; + // m_textures_by_hash is an alternative view of the texture cache + // All textures in here will also be in m_textures_by_address + TexHashCache m_textures_by_hash; - // bound_textures are actually active in the current draw + // m_bound_textures are actually active in the current draw // It's valid for textures to be in here after they've been invalidated - std::array bound_textures{}; + std::array m_bound_textures{}; - TexPool texture_pool; - u64 last_entry_id = 0; + TexPool m_texture_pool; + u64 m_last_entry_id = 0; // Backup configuration values struct BackupConfig @@ -437,7 +437,7 @@ private: bool graphics_mods; u32 graphics_mod_change_count; }; - BackupConfig backup_config = {}; + BackupConfig m_backup_config = {}; // Encoding texture used for EFB copies to RAM. std::unique_ptr m_efb_encoding_texture; From e6138d7683b8b86ad368f5056b424593cf36a10d Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 2 Sep 2023 09:12:42 +0200 Subject: [PATCH 071/120] Android: Fix controller float sliders crashing By not setting a stepSize, stepSize was getting set to the default value of 0, which is an Int. This later caused a crash when trying to cast it to Float. --- .../features/settings/model/view/FloatSliderSetting.kt | 3 ++- .../dolphinemu/features/settings/model/view/SliderSetting.kt | 2 ++ .../features/settings/ui/SettingsFragmentPresenter.kt | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt index c226e3e47e..7925903f5d 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt @@ -36,8 +36,9 @@ open class FloatSliderSetting : SliderSetting { min: Float, max: Float, units: String?, + stepSize: Float, showDecimal: Boolean - ) : super(name, description, min, max, units, showDecimal) { + ) : super(name, description, min, max, units, stepSize, showDecimal) { floatSetting = setting } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt index d3edccc025..6e93d74239 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt @@ -41,11 +41,13 @@ sealed class SliderSetting : SettingsItem { min: Any, max: Any, units: String?, + stepSize: Any, showDecimal: Boolean ) : super(name, description) { this.min = min this.max = max this.units = units + this.stepSize = stepSize this.showDecimal = showDecimal } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt index 15d678f752..fa5aaf4e53 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt @@ -2356,6 +2356,7 @@ class SettingsFragmentPresenter( ceil(setting.getDoubleMin()).toFloat(), floor(setting.getDoubleMax()).toFloat(), setting.getUiSuffix(), + 1.0f, false ) ) From 1c47c510cdd97dafe1ec9099b1724e84549ce8b4 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 2 Sep 2023 10:05:42 +0200 Subject: [PATCH 072/120] Android: Remove all uses of Any from SliderSetting This makes casting unnecessary, preventing the kind of type error we just had from occurring in the future. --- .../settings/model/view/FloatSliderSetting.kt | 18 ++++++++++--- .../settings/model/view/IntSliderSetting.kt | 11 ++++---- .../settings/model/view/SliderSetting.kt | 26 ++----------------- .../features/settings/ui/SettingsAdapter.kt | 12 ++++----- .../ui/viewholder/SliderViewHolder.kt | 8 +++--- 5 files changed, 32 insertions(+), 43 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt index 7925903f5d..ee07c64056 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt @@ -10,7 +10,11 @@ import java.math.BigDecimal import java.math.MathContext open class FloatSliderSetting : SliderSetting { - var floatSetting: AbstractFloatSetting + protected val floatSetting: AbstractFloatSetting + + val min: Float + val max: Float + val stepSize: Float override val setting: AbstractSetting get() = floatSetting @@ -25,8 +29,11 @@ open class FloatSliderSetting : SliderSetting { units: String?, stepSize: Float, showDecimal: Boolean - ) : super(context, titleId, descriptionId, min, max, units, stepSize, showDecimal) { + ) : super(context, titleId, descriptionId, units, showDecimal) { floatSetting = setting + this.min = min + this.max = max + this.stepSize = stepSize } constructor( @@ -38,11 +45,14 @@ open class FloatSliderSetting : SliderSetting { units: String?, stepSize: Float, showDecimal: Boolean - ) : super(name, description, min, max, units, stepSize, showDecimal) { + ) : super(name, description, units, showDecimal) { floatSetting = setting + this.min = min + this.max = max + this.stepSize = stepSize } - override val selectedValue: Float + open val selectedValue: Float get() = floatSetting.float open fun setSelectedValue(settings: Settings?, selection: Float) { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt index 09a415fc97..45f2a71e69 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt @@ -12,15 +12,16 @@ class IntSliderSetting( private val intSetting: AbstractIntSetting, titleId: Int, descriptionId: Int, - min: Int, - max: Int, + val min: Int, + val max: Int, units: String?, - stepSize: Int -) : SliderSetting(context, titleId, descriptionId, min, max, units, stepSize, false) { + val stepSize: Int +) : SliderSetting(context, titleId, descriptionId, units, false) { + override val setting: AbstractSetting get() = intSetting - override val selectedValue: Int + val selectedValue: Int get() = intSetting.int fun setSelectedValue(settings: Settings?, selection: Int) { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt index 6e93d74239..83b047f7a5 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt @@ -7,49 +7,27 @@ import android.content.Context sealed class SliderSetting : SettingsItem { override val type: Int = TYPE_SLIDER - var min: Any - private set - var max: Any - private set - var units: String? - private set - var stepSize: Any = 0 - private set - var showDecimal: Boolean = false - private set + val units: String? + val showDecimal: Boolean constructor( context: Context, nameId: Int, descriptionId: Int, - min: Any, - max: Any, units: String?, - stepSize: Any, showDecimal: Boolean ) : super(context, nameId, descriptionId) { - this.min = min - this.max = max this.units = units - this.stepSize = stepSize this.showDecimal = showDecimal } constructor( name: CharSequence, description: CharSequence?, - min: Any, - max: Any, units: String?, - stepSize: Any, showDecimal: Boolean ) : super(name, description) { - this.min = min - this.max = max this.units = units - this.stepSize = stepSize this.showDecimal = showDecimal } - - abstract val selectedValue: Any } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt index eff1b3ba13..b76d152d60 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt @@ -249,14 +249,14 @@ class SettingsAdapter( val slider = binding.slider when (item) { is FloatSliderSetting -> { - slider.valueFrom = item.min as Float - slider.valueTo = item.max as Float - slider.stepSize = item.stepSize as Float + slider.valueFrom = item.min + slider.valueTo = item.max + slider.stepSize = item.stepSize } is IntSliderSetting -> { - slider.valueFrom = (item.min as Int).toFloat() - slider.valueTo = (item.max as Int).toFloat() - slider.stepSize = (item.stepSize as Int).toFloat() + slider.valueFrom = item.min.toFloat() + slider.valueTo = item.max.toFloat() + slider.stepSize = item.stepSize.toFloat() } } slider.value = seekbarProgress diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt index 6dde8749c2..0ec51430c7 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt @@ -7,6 +7,7 @@ import android.text.TextUtils import android.view.View import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding +import org.dolphinemu.dolphinemu.features.settings.model.view.FloatSliderSetting import org.dolphinemu.dolphinemu.features.settings.model.view.IntSliderSetting import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem import org.dolphinemu.dolphinemu.features.settings.model.view.SliderSetting @@ -29,10 +30,9 @@ class SliderViewHolder( if (!TextUtils.isEmpty(item.description)) { binding.textSettingDescription.text = item.description } else { - val selectedValue: Float = if (item is IntSliderSetting) { - (setting.selectedValue as Int).toFloat() - } else { - setting.selectedValue as Float + val selectedValue: Float = when (item) { + is FloatSliderSetting -> item.selectedValue + is IntSliderSetting -> item.selectedValue.toFloat() } if (setting.showDecimal) { From 190e71a3188d1ade2a158c8aa0ec737b6929f5a8 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 2 Sep 2023 12:44:26 +0200 Subject: [PATCH 073/120] Android: Use JNI for setting/getting ISO paths This gets rid of the last Android-specific code that directly interfaces with INI files. --- .../features/settings/utils/SettingsFile.kt | 107 -------- .../dolphinemu/model/GameFileCache.kt | 94 ++----- .../dolphinemu/dolphinemu/utils/IniFile.java | 118 --------- Source/Android/jni/AndroidCommon/IDCache.cpp | 46 ---- Source/Android/jni/AndroidCommon/IDCache.h | 6 - Source/Android/jni/CMakeLists.txt | 1 - Source/Android/jni/GameList/GameFileCache.cpp | 13 + Source/Android/jni/IniFile.cpp | 245 ------------------ 8 files changed, 35 insertions(+), 595 deletions(-) delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.kt delete mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/IniFile.java delete mode 100644 Source/Android/jni/IniFile.cpp diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.kt deleted file mode 100644 index 827f04cbf3..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/utils/SettingsFile.kt +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.features.settings.utils - -import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView -import org.dolphinemu.dolphinemu.utils.BiMap -import org.dolphinemu.dolphinemu.utils.DirectoryInitialization -import org.dolphinemu.dolphinemu.utils.IniFile -import org.dolphinemu.dolphinemu.utils.Log -import java.io.File - -/** - * Contains static methods for interacting with .ini files in which settings are stored. - */ -object SettingsFile { - const val KEY_ISO_PATH_BASE = "ISOPath" - const val KEY_ISO_PATHS = "ISOPaths" - private val sectionsMap = BiMap() - - init { - sectionsMap.apply { - add("Hardware", "Video_Hardware") - add("Settings", "Video_Settings") - add("Enhancements", "Video_Enhancements") - add("Stereoscopy", "Video_Stereoscopy") - add("Hacks", "Video_Hacks") - add("GameSpecific", "Video") - } - } - - /** - * Reads a given .ini file from disk and returns it. - * If unsuccessful, outputs an error telling why it failed. - * - * @param file The ini file to load the settings from - * @param ini The object to load into - * @param view The current view. - */ - private fun readFile(file: File, ini: IniFile, view: SettingsActivityView) { - if (!ini.load(file, true)) { - Log.error("[SettingsFile] Error reading from: " + file.absolutePath) - view.onSettingsFileNotFound() - } - } - - fun readFile(fileName: String, ini: IniFile, view: SettingsActivityView) { - readFile(getSettingsFile(fileName), ini, view) - } - - /** - * Reads a given .ini file from disk and returns it. - * If unsuccessful, outputs an error telling why it failed. - * - * @param gameId the id of the game to load settings for. - * @param ini The object to load into - * @param view The current view. - */ - fun readCustomGameSettings( - gameId: String, - ini: IniFile, - view: SettingsActivityView - ) { - readFile(getCustomGameSettingsFile(gameId), ini, view) - } - - /** - * Saves a given .ini file on disk. - * If unsuccessful, outputs an error telling why it failed. - * - * @param fileName The target filename without a path or extension. - * @param ini The IniFile we want to serialize. - * @param view The current view. - */ - fun saveFile(fileName: String, ini: IniFile, view: SettingsActivityView) { - if (!ini.save(getSettingsFile(fileName))) { - Log.error("[SettingsFile] Error saving to: $fileName.ini") - view.showToastMessage("Error saving $fileName.ini") - } - } - - fun saveCustomGameSettings(gameId: String, ini: IniFile) { - ini.save(getCustomGameSettingsFile(gameId)) - } - - fun mapSectionNameFromIni(generalSectionName: String): String? { - return if (sectionsMap.getForward(generalSectionName) != null) { - sectionsMap.getForward(generalSectionName) - } else generalSectionName - } - - fun mapSectionNameToIni(generalSectionName: String): String? { - return if (sectionsMap.getBackward(generalSectionName) != null) { - sectionsMap.getBackward(generalSectionName) - } else generalSectionName - } - - @JvmStatic - fun getSettingsFile(fileName: String): File { - return File(DirectoryInitialization.getUserDirectory() + "/Config/" + fileName + ".ini") - } - - private fun getCustomGameSettingsFile(gameId: String): File { - return File( - DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini" - ) - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt index 74b1bd535a..97064f5521 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/model/GameFileCache.kt @@ -3,12 +3,9 @@ package org.dolphinemu.dolphinemu.model import androidx.annotation.Keep -import org.dolphinemu.dolphinemu.NativeLibrary import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting -import org.dolphinemu.dolphinemu.features.settings.model.Settings -import org.dolphinemu.dolphinemu.features.settings.utils.SettingsFile +import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig import org.dolphinemu.dolphinemu.utils.ContentHandler -import org.dolphinemu.dolphinemu.utils.IniFile import java.io.File class GameFileCache { @@ -57,89 +54,36 @@ class GameFileCache { private external fun newGameFileCache(): Long fun addGameFolder(path: String) { - val dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN) - val dolphinIni = IniFile(dolphinFile) - val pathSet = getPathSet(false) - val totalISOPaths = - dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0) + val paths = getIsoPaths() - if (!pathSet.contains(path)) { - dolphinIni.setInt( - Settings.SECTION_INI_GENERAL, - SettingsFile.KEY_ISO_PATHS, - totalISOPaths + 1 - ) - dolphinIni.setString( - Settings.SECTION_INI_GENERAL, - SettingsFile.KEY_ISO_PATH_BASE + totalISOPaths, - path - ) - dolphinIni.save(dolphinFile) - NativeLibrary.ReloadConfig() + if (!paths.contains(path)) { + setIsoPaths(paths + path) + NativeConfig.save(NativeConfig.LAYER_BASE) } } - private fun getPathSet(removeNonExistentFolders: Boolean): LinkedHashSet { - val dolphinFile = SettingsFile.getSettingsFile(Settings.FILE_DOLPHIN) - val dolphinIni = IniFile(dolphinFile) - val pathSet = LinkedHashSet() - val totalISOPaths = - dolphinIni.getInt(Settings.SECTION_INI_GENERAL, SettingsFile.KEY_ISO_PATHS, 0) + private fun getFolderPaths(removeNonExistentFolders: Boolean): Array { + val paths = getIsoPaths() - for (i in 0 until totalISOPaths) { - val path = dolphinIni.getString( - Settings.SECTION_INI_GENERAL, - SettingsFile.KEY_ISO_PATH_BASE + i, - "" - ) - - val pathExists = if (ContentHandler.isContentUri(path)) + val filteredPaths = paths.filter {path -> + if (ContentHandler.isContentUri(path)) ContentHandler.exists(path) else File(path).exists() - if (pathExists) { - pathSet.add(path) - } + }.toTypedArray() + + if (removeNonExistentFolders && paths.size > filteredPaths.size) { + setIsoPaths(filteredPaths) + NativeConfig.save(NativeConfig.LAYER_BASE) } - if (removeNonExistentFolders && totalISOPaths > pathSet.size) { - var setIndex = 0 - - dolphinIni.setInt( - Settings.SECTION_INI_GENERAL, - SettingsFile.KEY_ISO_PATHS, - pathSet.size - ) - - // One or more folders have been removed. - for (entry in pathSet) { - dolphinIni.setString( - Settings.SECTION_INI_GENERAL, - SettingsFile.KEY_ISO_PATH_BASE + setIndex, - entry - ) - setIndex++ - } - - // Delete known unnecessary keys. Ignore i values beyond totalISOPaths. - for (i in setIndex until totalISOPaths) { - dolphinIni.deleteKey( - Settings.SECTION_INI_GENERAL, - SettingsFile.KEY_ISO_PATH_BASE + i - ) - } - - dolphinIni.save(dolphinFile) - NativeLibrary.ReloadConfig() - } - return pathSet + return filteredPaths } @JvmStatic fun getAllGamePaths(): Array { val recursiveScan = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.boolean - val folderPathsSet = getPathSet(true) - val folderPaths = folderPathsSet.toTypedArray() + val folderPaths = getFolderPaths(true) return getAllGamePaths(folderPaths, recursiveScan) } @@ -148,5 +92,11 @@ class GameFileCache { folderPaths: Array, recursiveScan: Boolean ): Array + + @JvmStatic + external fun getIsoPaths(): Array + + @JvmStatic + external fun setIsoPaths(paths: Array) } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/IniFile.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/IniFile.java deleted file mode 100644 index 52aa638382..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/IniFile.java +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -package org.dolphinemu.dolphinemu.utils; - -import androidx.annotation.Keep; - -import java.io.File; - -// An in-memory copy of an INI file -public class IniFile -{ - // This class is non-static to ensure that the IniFile parent does not get garbage collected - // while a section still is accessible. (The finalizer of IniFile deletes the native sections.) - @SuppressWarnings("InnerClassMayBeStatic") - public class Section - { - @Keep - private long mPointer; - - @Keep - private Section(long pointer) - { - mPointer = pointer; - } - - public native boolean exists(String key); - - public native boolean delete(String key); - - public native String getString(String key, String defaultValue); - - public native boolean getBoolean(String key, boolean defaultValue); - - public native int getInt(String key, int defaultValue); - - public native float getFloat(String key, float defaultValue); - - public native void setString(String key, String newValue); - - public native void setBoolean(String key, boolean newValue); - - public native void setInt(String key, int newValue); - - public native void setFloat(String key, float newFloat); - } - - @Keep - private long mPointer; - - public IniFile() - { - mPointer = newIniFile(); - } - - public IniFile(IniFile other) - { - mPointer = copyIniFile(other); - } - - public IniFile(String path) - { - this(); - load(path, false); - } - - public IniFile(File file) - { - this(); - load(file, false); - } - - public native boolean load(String path, boolean keepCurrentData); - - public boolean load(File file, boolean keepCurrentData) - { - return load(file.getPath(), keepCurrentData); - } - - public native boolean save(String path); - - public boolean save(File file) - { - return save(file.getPath()); - } - - public native Section getOrCreateSection(String sectionName); - - public native boolean exists(String sectionName); - - public native boolean exists(String sectionName, String key); - - public native boolean deleteSection(String sectionName); - - public native boolean deleteKey(String sectionName, String key); - - public native String getString(String sectionName, String key, String defaultValue); - - public native boolean getBoolean(String sectionName, String key, boolean defaultValue); - - public native int getInt(String sectionName, String key, int defaultValue); - - public native float getFloat(String sectionName, String key, float defaultValue); - - public native void setString(String sectionName, String key, String newValue); - - public native void setBoolean(String sectionName, String key, boolean newValue); - - public native void setInt(String sectionName, String key, int newValue); - - public native void setFloat(String sectionName, String key, float newValue); - - @Override - public native void finalize(); - - private native long newIniFile(); - - private native long copyIniFile(IniFile other); -} diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 968d1b7e82..c3bd435af6 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -36,12 +36,6 @@ static jclass s_hash_map_class; static jmethodID s_hash_map_init; static jmethodID s_hash_map_put; -static jclass s_ini_file_class; -static jfieldID s_ini_file_pointer; -static jclass s_ini_file_section_class; -static jfieldID s_ini_file_section_pointer; -static jmethodID s_ini_file_section_constructor; - static jclass s_compress_cb_class; static jmethodID s_compress_cb_run; @@ -240,31 +234,6 @@ jmethodID GetHashMapPut() return s_hash_map_put; } -jclass GetIniFileClass() -{ - return s_ini_file_class; -} - -jfieldID GetIniFilePointer() -{ - return s_ini_file_pointer; -} - -jclass GetIniFileSectionClass() -{ - return s_ini_file_section_class; -} - -jfieldID GetIniFileSectionPointer() -{ - return s_ini_file_section_pointer; -} - -jmethodID GetIniFileSectionConstructor() -{ - return s_ini_file_section_constructor; -} - jclass GetCompressCallbackClass() { return s_compress_cb_class; @@ -581,19 +550,6 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) "(Ljava/lang/String;)Ljava/lang/String;"); env->DeleteLocalRef(analytics_class); - const jclass ini_file_class = env->FindClass("org/dolphinemu/dolphinemu/utils/IniFile"); - s_ini_file_class = reinterpret_cast(env->NewGlobalRef(ini_file_class)); - s_ini_file_pointer = env->GetFieldID(ini_file_class, "mPointer", "J"); - env->DeleteLocalRef(ini_file_class); - - const jclass ini_file_section_class = - env->FindClass("org/dolphinemu/dolphinemu/utils/IniFile$Section"); - s_ini_file_section_class = reinterpret_cast(env->NewGlobalRef(ini_file_section_class)); - s_ini_file_section_pointer = env->GetFieldID(ini_file_section_class, "mPointer", "J"); - s_ini_file_section_constructor = env->GetMethodID( - ini_file_section_class, "", "(Lorg/dolphinemu/dolphinemu/utils/IniFile;J)V"); - env->DeleteLocalRef(ini_file_section_class); - const jclass linked_hash_map_class = env->FindClass("java/util/LinkedHashMap"); s_linked_hash_map_class = reinterpret_cast(env->NewGlobalRef(linked_hash_map_class)); s_linked_hash_map_init = env->GetMethodID(s_linked_hash_map_class, "", "(I)V"); @@ -768,8 +724,6 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved) env->DeleteGlobalRef(s_analytics_class); env->DeleteGlobalRef(s_linked_hash_map_class); env->DeleteGlobalRef(s_hash_map_class); - env->DeleteGlobalRef(s_ini_file_class); - env->DeleteGlobalRef(s_ini_file_section_class); env->DeleteGlobalRef(s_compress_cb_class); env->DeleteGlobalRef(s_content_handler_class); env->DeleteGlobalRef(s_network_helper_class); diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h index dc8f4d51a1..eef5a8a085 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.h +++ b/Source/Android/jni/AndroidCommon/IDCache.h @@ -36,12 +36,6 @@ jclass GetHashMapClass(); jmethodID GetHashMapInit(); jmethodID GetHashMapPut(); -jclass GetIniFileClass(); -jfieldID GetIniFilePointer(); -jclass GetIniFileSectionClass(); -jfieldID GetIniFileSectionPointer(); -jmethodID GetIniFileSectionConstructor(); - jclass GetCompressCallbackClass(); jmethodID GetCompressCallbackRun(); diff --git a/Source/Android/jni/CMakeLists.txt b/Source/Android/jni/CMakeLists.txt index bb849074e4..bb18440b02 100644 --- a/Source/Android/jni/CMakeLists.txt +++ b/Source/Android/jni/CMakeLists.txt @@ -27,7 +27,6 @@ add_library(main SHARED Input/MappingCommon.cpp Input/NumericSetting.cpp Input/NumericSetting.h - IniFile.cpp MainAndroid.cpp RiivolutionPatches.cpp SkylanderConfig.cpp diff --git a/Source/Android/jni/GameList/GameFileCache.cpp b/Source/Android/jni/GameList/GameFileCache.cpp index 280de59bf9..27c3b0ef63 100644 --- a/Source/Android/jni/GameList/GameFileCache.cpp +++ b/Source/Android/jni/GameList/GameFileCache.cpp @@ -6,6 +6,7 @@ #include +#include "Core/Config/MainSettings.h" #include "UICommon/GameFileCache.h" #include "jni/AndroidCommon/AndroidCommon.h" #include "jni/AndroidCommon/IDCache.h" @@ -38,6 +39,18 @@ JNIEXPORT jobjectArray JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCach env, UICommon::FindAllGamePaths(JStringArrayToVector(env, folder_paths), recursive_scan)); } +JNIEXPORT jobjectArray JNICALL +Java_org_dolphinemu_dolphinemu_model_GameFileCache_getIsoPaths(JNIEnv* env, jclass) +{ + return VectorToJStringArray(env, Config::GetIsoPaths()); +} + +JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_setIsoPaths( + JNIEnv* env, jclass, jobjectArray paths) +{ + Config::SetIsoPaths(JStringArrayToVector(env, paths)); +} + JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_model_GameFileCache_getSize(JNIEnv* env, jobject obj) { diff --git a/Source/Android/jni/IniFile.cpp b/Source/Android/jni/IniFile.cpp deleted file mode 100644 index 7172094bdf..0000000000 --- a/Source/Android/jni/IniFile.cpp +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright 2020 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include - -#include "Common/IniFile.h" -#include "jni/AndroidCommon/AndroidCommon.h" -#include "jni/AndroidCommon/IDCache.h" - -using Common::IniFile; - -static IniFile::Section* GetSectionPointer(JNIEnv* env, jobject obj) -{ - return reinterpret_cast( - env->GetLongField(obj, IDCache::GetIniFileSectionPointer())); -} - -static IniFile* GetIniFilePointer(JNIEnv* env, jobject obj) -{ - return reinterpret_cast(env->GetLongField(obj, IDCache::GetIniFilePointer())); -} - -static jobject SectionToJava(JNIEnv* env, jobject ini_file, IniFile::Section* section) -{ - if (!section) - return nullptr; - - return env->NewObject(IDCache::GetIniFileSectionClass(), IDCache::GetIniFileSectionConstructor(), - ini_file, reinterpret_cast(section)); -} - -template -static T GetInSection(JNIEnv* env, jobject obj, jstring key, T default_value) -{ - T result; - GetSectionPointer(env, obj)->Get(GetJString(env, key), &result, default_value); - return result; -} - -template -static void SetInSection(JNIEnv* env, jobject obj, jstring key, T new_value) -{ - GetSectionPointer(env, obj)->Set(GetJString(env, key), new_value); -} - -template -static T Get(JNIEnv* env, jobject obj, jstring section_name, jstring key, T default_value) -{ - T result; - GetIniFilePointer(env, obj) - ->GetOrCreateSection(GetJString(env, section_name)) - ->Get(GetJString(env, key), &result, default_value); - return result; -} - -template -static void Set(JNIEnv* env, jobject obj, jstring section_name, jstring key, T new_value) -{ - GetIniFilePointer(env, obj) - ->GetOrCreateSection(GetJString(env, section_name)) - ->Set(GetJString(env, key), new_value); -} - -extern "C" { - -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_00024Section_exists( - JNIEnv* env, jobject obj, jstring key) -{ - return static_cast(GetSectionPointer(env, obj)->Exists(GetJString(env, key))); -} - -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_00024Section_delete( - JNIEnv* env, jobject obj, jstring key) -{ - return static_cast(GetSectionPointer(env, obj)->Delete(GetJString(env, key))); -} - -JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_00024Section_getString( - JNIEnv* env, jobject obj, jstring key, jstring default_value) -{ - return ToJString(env, GetInSection(env, obj, key, GetJString(env, default_value))); -} - -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_00024Section_getBoolean( - JNIEnv* env, jobject obj, jstring key, jboolean default_value) -{ - return static_cast(GetInSection(env, obj, key, static_cast(default_value))); -} - -JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_00024Section_getInt( - JNIEnv* env, jobject obj, jstring key, jint default_value) -{ - return GetInSection(env, obj, key, default_value); -} - -JNIEXPORT jfloat JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_00024Section_getFloat( - JNIEnv* env, jobject obj, jstring key, jfloat default_value) -{ - return GetInSection(env, obj, key, default_value); -} - -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_00024Section_setString( - JNIEnv* env, jobject obj, jstring key, jstring new_value) -{ - SetInSection(env, obj, key, GetJString(env, new_value)); -} - -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_00024Section_setBoolean( - JNIEnv* env, jobject obj, jstring key, jboolean new_value) -{ - SetInSection(env, obj, key, static_cast(new_value)); -} - -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_00024Section_setInt( - JNIEnv* env, jobject obj, jstring key, jint new_value) -{ - SetInSection(env, obj, key, new_value); -} - -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_00024Section_setFloat( - JNIEnv* env, jobject obj, jstring key, jfloat new_value) -{ - SetInSection(env, obj, key, new_value); -} - -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_load( - JNIEnv* env, jobject obj, jstring path, jboolean keep_current_data) -{ - return static_cast( - GetIniFilePointer(env, obj)->Load(GetJString(env, path), keep_current_data)); -} - -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_save(JNIEnv* env, - jobject obj, - jstring path) -{ - return static_cast(GetIniFilePointer(env, obj)->Save(GetJString(env, path))); -} - -JNIEXPORT jobject JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_getOrCreateSection( - JNIEnv* env, jobject obj, jstring section_name) -{ - return SectionToJava( - env, obj, GetIniFilePointer(env, obj)->GetOrCreateSection(GetJString(env, section_name))); -} - -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_exists__Ljava_lang_String_2( - JNIEnv* env, jobject obj, jstring section_name) -{ - return static_cast(GetIniFilePointer(env, obj)->Exists(GetJString(env, section_name))); -} - -JNIEXPORT jboolean JNICALL -Java_org_dolphinemu_dolphinemu_utils_IniFile_exists__Ljava_lang_String_2Ljava_lang_String_2( - JNIEnv* env, jobject obj, jstring section_name, jstring key) -{ - return static_cast( - GetIniFilePointer(env, obj)->Exists(GetJString(env, section_name), GetJString(env, key))); -} - -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_deleteSection( - JNIEnv* env, jobject obj, jstring section_name) -{ - return static_cast( - GetIniFilePointer(env, obj)->DeleteSection(GetJString(env, section_name))); -} - -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_deleteKey( - JNIEnv* env, jobject obj, jstring section_name, jstring key) -{ - return static_cast( - GetIniFilePointer(env, obj)->DeleteKey(GetJString(env, section_name), GetJString(env, key))); -} - -JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_getString( - JNIEnv* env, jobject obj, jstring section_name, jstring key, jstring default_value) -{ - return ToJString(env, Get(env, obj, section_name, key, GetJString(env, default_value))); -} - -JNIEXPORT jboolean JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_getBoolean( - JNIEnv* env, jobject obj, jstring section_name, jstring key, jboolean default_value) -{ - return static_cast(Get(env, obj, section_name, key, static_cast(default_value))); -} - -JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_getInt(JNIEnv* env, jobject obj, - jstring section_name, - jstring key, - jint default_value) -{ - return Get(env, obj, section_name, key, default_value); -} - -JNIEXPORT jfloat JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_getFloat( - JNIEnv* env, jobject obj, jstring section_name, jstring key, jfloat default_value) -{ - return Get(env, obj, section_name, key, default_value); -} - -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_setString( - JNIEnv* env, jobject obj, jstring section_name, jstring key, jstring new_value) -{ - Set(env, obj, section_name, key, GetJString(env, new_value)); -} - -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_setBoolean( - JNIEnv* env, jobject obj, jstring section_name, jstring key, jboolean new_value) -{ - Set(env, obj, section_name, key, static_cast(new_value)); -} - -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_setInt(JNIEnv* env, jobject obj, - jstring section_name, - jstring key, - jint new_value) -{ - Set(env, obj, section_name, key, new_value); -} - -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_setFloat( - JNIEnv* env, jobject obj, jstring section_name, jstring key, jfloat new_value) -{ - Set(env, obj, section_name, key, new_value); -} - -JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_finalize(JNIEnv* env, - jobject obj) -{ - delete GetIniFilePointer(env, obj); -} - -JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_newIniFile(JNIEnv* env, - jobject) -{ - return reinterpret_cast(new IniFile); -} - -JNIEXPORT jlong JNICALL Java_org_dolphinemu_dolphinemu_utils_IniFile_copyIniFile(JNIEnv* env, - jobject, - jobject other) -{ - return reinterpret_cast(new IniFile(*GetIniFilePointer(env, other))); -} -} From 9cabf20aaa058fc4cfdd4990dd9abf4e9c523382 Mon Sep 17 00:00:00 2001 From: Dentomologist Date: Tue, 29 Aug 2023 19:27:59 -0700 Subject: [PATCH 074/120] Fifo: Convert MemoryUpdate::Type to enum class --- Source/Core/Core/FifoPlayer/FifoDataFile.cpp | 2 +- Source/Core/Core/FifoPlayer/FifoDataFile.h | 8 ++++---- Source/Core/Core/FifoPlayer/FifoRecorder.cpp | 4 ++-- Source/Core/VideoCommon/BPStructs.cpp | 4 ++-- Source/Core/VideoCommon/TextureCacheBase.cpp | 7 ++++--- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Source/Core/Core/FifoPlayer/FifoDataFile.cpp b/Source/Core/Core/FifoPlayer/FifoDataFile.cpp index 1967bf7f83..c19a07f6e1 100644 --- a/Source/Core/Core/FifoPlayer/FifoDataFile.cpp +++ b/Source/Core/Core/FifoPlayer/FifoDataFile.cpp @@ -385,7 +385,7 @@ u64 FifoDataFile::WriteMemoryUpdates(const std::vector& memUpdates dstUpdate.dataOffset = dataOffset; dstUpdate.dataSize = static_cast(srcUpdate.data.size()); dstUpdate.fifoPosition = srcUpdate.fifoPosition; - dstUpdate.type = srcUpdate.type; + dstUpdate.type = static_cast(srcUpdate.type); u64 updateOffset = updateListOffset + (i * sizeof(FileMemoryUpdate)); file.Seek(updateOffset, File::SeekOrigin::Begin); diff --git a/Source/Core/Core/FifoPlayer/FifoDataFile.h b/Source/Core/Core/FifoPlayer/FifoDataFile.h index 236f82e8b9..64167a75b6 100644 --- a/Source/Core/Core/FifoPlayer/FifoDataFile.h +++ b/Source/Core/Core/FifoPlayer/FifoDataFile.h @@ -18,11 +18,11 @@ class IOFile; struct MemoryUpdate { - enum Type + enum class Type : u8 { - TEXTURE_MAP = 0x01, - XF_DATA = 0x02, - VERTEX_STREAM = 0x04, + TextureMap = 0x01, + XFData = 0x02, + VertexStream = 0x04, TMEM = 0x08, }; diff --git a/Source/Core/Core/FifoPlayer/FifoRecorder.cpp b/Source/Core/Core/FifoPlayer/FifoRecorder.cpp index 7cedf8299b..e24ef2a9af 100644 --- a/Source/Core/Core/FifoPlayer/FifoRecorder.cpp +++ b/Source/Core/Core/FifoPlayer/FifoRecorder.cpp @@ -69,7 +69,7 @@ void FifoRecorder::FifoRecordAnalyzer::OnIndexedLoad(CPArray array, u32 index, u { const u32 load_address = m_cpmem.array_bases[array] + m_cpmem.array_strides[array] * index; - m_owner->UseMemory(load_address, size * sizeof(u32), MemoryUpdate::XF_DATA); + m_owner->UseMemory(load_address, size * sizeof(u32), MemoryUpdate::Type::XFData); } // TODO: The following code is copied with modifications from VertexLoaderBase. @@ -210,7 +210,7 @@ void FifoRecorder::FifoRecordAnalyzer::ProcessVertexComponent( const u32 array_start = m_cpmem.array_bases[array_index] + byte_offset; const u32 array_size = m_cpmem.array_strides[array_index] * max_index + component_size; - m_owner->UseMemory(array_start, array_size, MemoryUpdate::VERTEX_STREAM); + m_owner->UseMemory(array_start, array_size, MemoryUpdate::Type::VertexStream); } static FifoRecorder instance; diff --git a/Source/Core/VideoCommon/BPStructs.cpp b/Source/Core/VideoCommon/BPStructs.cpp index ca1dea09ed..0beb464eae 100644 --- a/Source/Core/VideoCommon/BPStructs.cpp +++ b/Source/Core/VideoCommon/BPStructs.cpp @@ -397,7 +397,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, memory.CopyFromEmu(texMem + tlutTMemAddr, addr, tlutXferCount); if (OpcodeDecoder::g_record_fifo_data) - FifoRecorder::GetInstance().UseMemory(addr, tlutXferCount, MemoryUpdate::TMEM); + FifoRecorder::GetInstance().UseMemory(addr, tlutXferCount, MemoryUpdate::Type::TMEM); TMEM::InvalidateAll(); @@ -615,7 +615,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, } if (OpcodeDecoder::g_record_fifo_data) - FifoRecorder::GetInstance().UseMemory(src_addr, bytes_read, MemoryUpdate::TMEM); + FifoRecorder::GetInstance().UseMemory(src_addr, bytes_read, MemoryUpdate::Type::TMEM); TMEM::InvalidateAll(); } diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index 37dadba967..8d7a9f875d 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -1379,8 +1379,9 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp // its own memory modification tracking independent of the texture hashing below. if (OpcodeDecoder::g_record_fifo_data && !texture_info.IsFromTmem()) { - FifoRecorder::GetInstance().UseMemory( - texture_info.GetRawAddress(), texture_info.GetFullLevelSize(), MemoryUpdate::TEXTURE_MAP); + FifoRecorder::GetInstance().UseMemory(texture_info.GetRawAddress(), + texture_info.GetFullLevelSize(), + MemoryUpdate::Type::TextureMap); } // TODO: This doesn't hash GB tiles for preloaded RGBA8 textures (instead, it's hashing more data @@ -2541,7 +2542,7 @@ void TextureCacheBase::CopyRenderTargetToTexture( u32 address = dstAddr; for (u32 i = 0; i < num_blocks_y; i++) { - FifoRecorder::GetInstance().UseMemory(address, bytes_per_row, MemoryUpdate::TEXTURE_MAP, + FifoRecorder::GetInstance().UseMemory(address, bytes_per_row, MemoryUpdate::Type::TextureMap, true); address += dstStride; } From 62fee2f3b60c84b27efbb0792e3695bbe812ac1a Mon Sep 17 00:00:00 2001 From: iwubcode Date: Sun, 13 Aug 2023 16:09:45 -0500 Subject: [PATCH 075/120] VideoCommon: add loading cube maps from DDS files and loading it into our custom texture object. Custom texture object now has the concept of slices in addition to levels. Traditional custom textures have a single slice --- .../VideoCommon/Assets/CustomAssetLibrary.cpp | 92 +++++++------ .../VideoCommon/Assets/CustomTextureData.cpp | 129 ++++++++++++------ .../VideoCommon/Assets/CustomTextureData.h | 23 ++-- .../Assets/DirectFilesystemAssetLibrary.cpp | 32 +++-- .../Assets/DirectFilesystemAssetLibrary.h | 5 +- .../Core/VideoCommon/Assets/TextureAsset.cpp | 23 +++- .../Runtime/Actions/CustomPipelineAction.cpp | 16 ++- Source/Core/VideoCommon/TextureCacheBase.cpp | 23 ++-- 8 files changed, 225 insertions(+), 118 deletions(-) diff --git a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.cpp index 025da4559b..8a78fed490 100644 --- a/Source/Core/VideoCommon/Assets/CustomAssetLibrary.cpp +++ b/Source/Core/VideoCommon/Assets/CustomAssetLibrary.cpp @@ -15,9 +15,12 @@ namespace std::size_t GetAssetSize(const CustomTextureData& data) { std::size_t total = 0; - for (const auto& level : data.m_levels) + for (const auto& slice : data.m_slices) { - total += level.data.size(); + for (const auto& level : slice.m_levels) + { + total += level.data.size(); + } } return total; } @@ -30,51 +33,58 @@ CustomAssetLibrary::LoadInfo CustomAssetLibrary::LoadGameTexture(const AssetID& return {}; // Note: 'LoadTexture()' ensures we have a level loaded - const auto& first_mip = data->m_levels[0]; - - // Verify that each mip level is the correct size (divide by 2 each time). - u32 current_mip_width = first_mip.width; - u32 current_mip_height = first_mip.height; - for (u32 mip_level = 1; mip_level < static_cast(data->m_levels.size()); mip_level++) + for (std::size_t slice_index = 0; slice_index < data->m_slices.size(); slice_index++) { - if (current_mip_width != 1 || current_mip_height != 1) - { - current_mip_width = std::max(current_mip_width / 2, 1u); - current_mip_height = std::max(current_mip_height / 2, 1u); + auto& slice = data->m_slices[slice_index]; + const auto& first_mip = slice.m_levels[0]; - const VideoCommon::CustomTextureData::Level& level = data->m_levels[mip_level]; - if (current_mip_width == level.width && current_mip_height == level.height) - continue; - - ERROR_LOG_FMT(VIDEO, - "Invalid custom game texture size {}x{} for texture asset {}. Mipmap level {} " - "must be {}x{}.", - level.width, level.height, asset_id, mip_level, current_mip_width, - current_mip_height); - } - else + // Verify that each mip level is the correct size (divide by 2 each time). + u32 current_mip_width = first_mip.width; + u32 current_mip_height = first_mip.height; + for (u32 mip_level = 1; mip_level < static_cast(slice.m_levels.size()); mip_level++) { - // It is invalid to have more than a single 1x1 mipmap. - ERROR_LOG_FMT(VIDEO, - "Custom game texture {} has too many 1x1 mipmaps. Skipping extra levels.", - asset_id); + if (current_mip_width != 1 || current_mip_height != 1) + { + current_mip_width = std::max(current_mip_width / 2, 1u); + current_mip_height = std::max(current_mip_height / 2, 1u); + + const VideoCommon::CustomTextureData::ArraySlice::Level& level = slice.m_levels[mip_level]; + if (current_mip_width == level.width && current_mip_height == level.height) + continue; + + ERROR_LOG_FMT(VIDEO, + "Invalid custom game texture size {}x{} for texture asset {}. Slice {} with " + "mipmap level {} " + "must be {}x{}.", + level.width, level.height, asset_id, slice_index, mip_level, + current_mip_width, current_mip_height); + } + else + { + // It is invalid to have more than a single 1x1 mipmap. + ERROR_LOG_FMT( + VIDEO, + "Custom game texture {} has too many 1x1 mipmaps for slice {}. Skipping extra levels.", + asset_id, slice_index); + } + + // Drop this mip level and any others after it. + while (slice.m_levels.size() > mip_level) + slice.m_levels.pop_back(); } - // Drop this mip level and any others after it. - while (data->m_levels.size() > mip_level) - data->m_levels.pop_back(); - } + // All levels have to have the same format. + if (std::any_of(slice.m_levels.begin(), slice.m_levels.end(), + [&first_mip](const VideoCommon::CustomTextureData::ArraySlice::Level& l) { + return l.format != first_mip.format; + })) + { + ERROR_LOG_FMT( + VIDEO, "Custom game texture {} has inconsistent formats across mip levels for slice {}.", + asset_id, slice_index); - // All levels have to have the same format. - if (std::any_of(data->m_levels.begin(), data->m_levels.end(), - [&first_mip](const VideoCommon::CustomTextureData::Level& l) { - return l.format != first_mip.format; - })) - { - ERROR_LOG_FMT(VIDEO, "Custom game texture {} has inconsistent formats across mip levels.", - asset_id); - - return {}; + return {}; + } } return load_info; diff --git a/Source/Core/VideoCommon/Assets/CustomTextureData.cpp b/Source/Core/VideoCommon/Assets/CustomTextureData.cpp index 04183da3f0..23af429d11 100644 --- a/Source/Core/VideoCommon/Assets/CustomTextureData.cpp +++ b/Source/Core/VideoCommon/Assets/CustomTextureData.cpp @@ -62,6 +62,19 @@ struct DDS_PIXELFORMAT #define DDS_PAL8A 0x00000021 // DDPF_PALETTEINDEXED8 | DDPF_ALPHAPIXELS #define DDS_BUMPDUDV 0x00080000 // DDPF_BUMPDUDV +#define DDS_CUBEMAP_POSITIVEX 0x00000600 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX +#define DDS_CUBEMAP_NEGATIVEX 0x00000a00 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX +#define DDS_CUBEMAP_POSITIVEY 0x00001200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY +#define DDS_CUBEMAP_NEGATIVEY 0x00002200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY +#define DDS_CUBEMAP_POSITIVEZ 0x00004200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ +#define DDS_CUBEMAP_NEGATIVEZ 0x00008200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ + +#define DDS_CUBEMAP_ALLFACES \ + (DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_NEGATIVEX | DDS_CUBEMAP_POSITIVEY | DDS_CUBEMAP_NEGATIVEY | \ + DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ) + +#define DDS_CUBEMAP 0x00000200 // DDSCAPS2_CUBEMAP + #ifndef MAKEFOURCC #define MAKEFOURCC(ch0, ch1, ch2, ch3) \ ((uint32_t)(uint8_t)(ch0) | ((uint32_t)(uint8_t)(ch1) << 8) | ((uint32_t)(uint8_t)(ch2) << 16) | \ @@ -143,12 +156,13 @@ struct DDSLoadInfo u32 width = 0; u32 height = 0; u32 mip_count = 0; + u32 array_size = 0; AbstractTextureFormat format = AbstractTextureFormat::RGBA8; size_t first_mip_offset = 0; size_t first_mip_size = 0; u32 first_mip_row_length = 0; - std::function conversion_function; + std::function conversion_function; }; static constexpr u32 GetBlockCount(u32 extent, u32 block_size) @@ -171,7 +185,7 @@ static u32 CalculateMipCount(u32 width, u32 height) return mip_count; } -static void ConvertTexture_X8B8G8R8(VideoCommon::CustomTextureData::Level* level) +static void ConvertTexture_X8B8G8R8(VideoCommon::CustomTextureData::ArraySlice::Level* level) { u8* data_ptr = level->data.data(); for (u32 row = 0; row < level->height; row++) @@ -185,7 +199,7 @@ static void ConvertTexture_X8B8G8R8(VideoCommon::CustomTextureData::Level* level } } -static void ConvertTexture_A8R8G8B8(VideoCommon::CustomTextureData::Level* level) +static void ConvertTexture_A8R8G8B8(VideoCommon::CustomTextureData::ArraySlice::Level* level) { u8* data_ptr = level->data.data(); for (u32 row = 0; row < level->height; row++) @@ -202,7 +216,7 @@ static void ConvertTexture_A8R8G8B8(VideoCommon::CustomTextureData::Level* level } } -static void ConvertTexture_X8R8G8B8(VideoCommon::CustomTextureData::Level* level) +static void ConvertTexture_X8R8G8B8(VideoCommon::CustomTextureData::ArraySlice::Level* level) { u8* data_ptr = level->data.data(); for (u32 row = 0; row < level->height; row++) @@ -219,7 +233,7 @@ static void ConvertTexture_X8R8G8B8(VideoCommon::CustomTextureData::Level* level } } -static void ConvertTexture_R8G8B8(VideoCommon::CustomTextureData::Level* level) +static void ConvertTexture_R8G8B8(VideoCommon::CustomTextureData::ArraySlice::Level* level) { std::vector new_data(level->row_length * level->height * sizeof(u32)); @@ -297,13 +311,26 @@ static bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info) if (!file.ReadBytes(&dxt10_header, sizeof(dxt10_header))) return false; - // Can't handle array textures here. Doesn't make sense to use them, anyway. - if (dxt10_header.resourceDimension != DDS_DIMENSION_TEXTURE2D || dxt10_header.arraySize != 1) - return false; - + info->array_size = dxt10_header.arraySize; header_size += sizeof(dxt10_header); dxt10_format = dxt10_header.dxgiFormat; } + else + { + if (header.dwCaps2 & DDS_CUBEMAP) + { + if ((header.dwCaps2 & DDS_CUBEMAP_ALLFACES) != DDS_CUBEMAP_ALLFACES) + { + return false; + } + + info->array_size = 6; + } + else + { + info->array_size = 1; + } + } // Currently, we only handle compressed textures here, and leave the rest to the SOIL loader. // In the future, this could be extended, but these isn't much benefit in doing so currently. @@ -369,6 +396,20 @@ static bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info) return false; } + if (header.dwCaps2 & DDS_CUBEMAP) + { + if ((header.dwCaps2 & DDS_CUBEMAP_ALLFACES) != DDS_CUBEMAP_ALLFACES) + { + return false; + } + + info->array_size = 6; + } + else + { + info->array_size = 1; + } + // All these formats are RGBA, just with byte swapping. info->format = AbstractTextureFormat::RGBA8; info->block_size = 1; @@ -416,9 +457,10 @@ static bool ParseDDSHeader(File::IOFile& file, DDSLoadInfo* info) return true; } -static bool ReadMipLevel(VideoCommon::CustomTextureData::Level* level, File::IOFile& file, - const std::string& filename, u32 mip_level, const DDSLoadInfo& info, - u32 width, u32 height, u32 row_length, size_t size) +static bool ReadMipLevel(VideoCommon::CustomTextureData::ArraySlice::Level* level, + File::IOFile& file, const std::string& filename, u32 mip_level, + const DDSLoadInfo& info, u32 width, u32 height, u32 row_length, + size_t size) { // D3D11 cannot handle block compressed textures where the first mip level is // not a multiple of the block size. @@ -463,43 +505,50 @@ bool LoadDDSTexture(CustomTextureData* texture, const std::string& filename) if (!ParseDDSHeader(file, &info)) return false; - // Read first mip level, as it may have a custom pitch. - CustomTextureData::Level first_level; - if (!file.Seek(info.first_mip_offset, File::SeekOrigin::Begin) || - !ReadMipLevel(&first_level, file, filename, 0, info, info.width, info.height, - info.first_mip_row_length, info.first_mip_size)) - { + if (!file.Seek(info.first_mip_offset, File::SeekOrigin::Begin)) return false; - } - texture->m_levels.push_back(std::move(first_level)); - - // Read in any remaining mip levels in the file. - // If the .dds file does not contain a full mip chain, we'll fall back to the old path. - u32 mip_width = info.width; - u32 mip_height = info.height; - for (u32 i = 1; i < info.mip_count; i++) + for (u32 arr_i = 0; arr_i < info.array_size; arr_i++) { - mip_width = std::max(mip_width / 2, 1u); - mip_height = std::max(mip_height / 2, 1u); + auto& slice = texture->m_slices.emplace_back(); + // Read first mip level, as it may have a custom pitch. + CustomTextureData::ArraySlice::Level first_level; + if (!ReadMipLevel(&first_level, file, filename, 0, info, info.width, info.height, + info.first_mip_row_length, info.first_mip_size)) + { + return false; + } - // Pitch can't be specified with each mip level, so we have to calculate it ourselves. - u32 blocks_wide = GetBlockCount(mip_width, info.block_size); - u32 blocks_high = GetBlockCount(mip_height, info.block_size); - u32 mip_row_length = blocks_wide * info.block_size; - size_t mip_size = blocks_wide * static_cast(info.bytes_per_block) * blocks_high; - CustomTextureData::Level level; - if (!ReadMipLevel(&level, file, filename, i, info, mip_width, mip_height, mip_row_length, - mip_size)) - break; + slice.m_levels.push_back(std::move(first_level)); - texture->m_levels.push_back(std::move(level)); + // Read in any remaining mip levels in the file. + // If the .dds file does not contain a full mip chain, we'll fall back to the old path. + u32 mip_width = info.width; + u32 mip_height = info.height; + for (u32 i = 1; i < info.mip_count; i++) + { + mip_width = std::max(mip_width / 2, 1u); + mip_height = std::max(mip_height / 2, 1u); + + // Pitch can't be specified with each mip level, so we have to calculate it ourselves. + u32 blocks_wide = GetBlockCount(mip_width, info.block_size); + u32 blocks_high = GetBlockCount(mip_height, info.block_size); + u32 mip_row_length = blocks_wide * info.block_size; + size_t mip_size = blocks_wide * static_cast(info.bytes_per_block) * blocks_high; + CustomTextureData::ArraySlice::Level level; + if (!ReadMipLevel(&level, file, filename, i, info, mip_width, mip_height, mip_row_length, + mip_size)) + break; + + slice.m_levels.push_back(std::move(level)); + } } return true; } -bool LoadDDSTexture(CustomTextureData::Level* level, const std::string& filename, u32 mip_level) +bool LoadDDSTexture(CustomTextureData::ArraySlice::Level* level, const std::string& filename, + u32 mip_level) { // Only loading a single mip level. File::IOFile file; @@ -515,7 +564,7 @@ bool LoadDDSTexture(CustomTextureData::Level* level, const std::string& filename info.first_mip_row_length, info.first_mip_size); } -bool LoadPNGTexture(CustomTextureData::Level* level, const std::string& filename) +bool LoadPNGTexture(CustomTextureData::ArraySlice::Level* level, const std::string& filename) { if (!level) [[unlikely]] return false; diff --git a/Source/Core/VideoCommon/Assets/CustomTextureData.h b/Source/Core/VideoCommon/Assets/CustomTextureData.h index b7fd6f379e..fe15c05eaa 100644 --- a/Source/Core/VideoCommon/Assets/CustomTextureData.h +++ b/Source/Core/VideoCommon/Assets/CustomTextureData.h @@ -14,18 +14,23 @@ namespace VideoCommon class CustomTextureData { public: - struct Level + struct ArraySlice { - std::vector data; - AbstractTextureFormat format = AbstractTextureFormat::RGBA8; - u32 width = 0; - u32 height = 0; - u32 row_length = 0; + struct Level + { + std::vector data; + AbstractTextureFormat format = AbstractTextureFormat::RGBA8; + u32 width = 0; + u32 height = 0; + u32 row_length = 0; + }; + std::vector m_levels; }; - std::vector m_levels; + std::vector m_slices; }; bool LoadDDSTexture(CustomTextureData* texture, const std::string& filename); -bool LoadDDSTexture(CustomTextureData::Level* level, const std::string& filename, u32 mip_level); -bool LoadPNGTexture(CustomTextureData::Level* level, const std::string& filename); +bool LoadDDSTexture(CustomTextureData::ArraySlice::Level* level, const std::string& filename, + u32 mip_level); +bool LoadPNGTexture(CustomTextureData::ArraySlice::Level* level, const std::string& filename); } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp index 7a1f84ca08..99c3e9487e 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.cpp @@ -9,7 +9,6 @@ #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/StringUtil.h" -#include "VideoCommon/Assets/CustomTextureData.h" #include "VideoCommon/Assets/MaterialAsset.h" #include "VideoCommon/Assets/ShaderAsset.h" @@ -34,9 +33,12 @@ std::chrono::system_clock::time_point FileTimeToSysTime(std::filesystem::file_ti std::size_t GetAssetSize(const CustomTextureData& data) { std::size_t total = 0; - for (const auto& level : data.m_levels) + for (const auto& slice : data.m_slices) { - total += level.data.size(); + for (const auto& level : slice.m_levels) + { + total += level.data.size(); + } } return total; } @@ -247,24 +249,32 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass return {}; } - if (!LoadMips(asset_path, data)) + if (data->m_slices.empty()) [[unlikely]] + data->m_slices.push_back({}); + + if (!LoadMips(asset_path, &data->m_slices[0])) return {}; return LoadInfo{GetAssetSize(*data), FileTimeToSysTime(last_loaded_time)}; } else if (ext == ".png") { - // If we have no levels, create one to pass into LoadPNGTexture - if (data->m_levels.empty()) - data->m_levels.push_back({}); + // If we have no slices, create one + if (data->m_slices.empty()) + data->m_slices.push_back({}); - if (!LoadPNGTexture(&data->m_levels[0], PathToString(asset_path))) + auto& slice = data->m_slices[0]; + // If we have no levels, create one to pass into LoadPNGTexture + if (slice.m_levels.empty()) + slice.m_levels.push_back({}); + + if (!LoadPNGTexture(&slice.m_levels[0], PathToString(asset_path))) { ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load png texture!", asset_id); return {}; } - if (!LoadMips(asset_path, data)) + if (!LoadMips(asset_path, &slice)) return {}; return LoadInfo{GetAssetSize(*data), FileTimeToSysTime(last_loaded_time)}; @@ -282,7 +292,7 @@ void DirectFilesystemAssetLibrary::SetAssetIDMapData(const AssetID& asset_id, } bool DirectFilesystemAssetLibrary::LoadMips(const std::filesystem::path& asset_path, - CustomTextureData* data) + CustomTextureData::ArraySlice* data) { if (!data) [[unlikely]] return false; @@ -304,7 +314,7 @@ bool DirectFilesystemAssetLibrary::LoadMips(const std::filesystem::path& asset_p if (!File::Exists(full_path)) return true; - VideoCommon::CustomTextureData::Level level; + VideoCommon::CustomTextureData::ArraySlice::Level level; if (extension_lower == ".dds") { if (!LoadDDSTexture(&level, full_path, mip_level)) diff --git a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h index eeedb42d68..21c5ce7d54 100644 --- a/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h +++ b/Source/Core/VideoCommon/Assets/DirectFilesystemAssetLibrary.h @@ -9,11 +9,10 @@ #include #include "VideoCommon/Assets/CustomAssetLibrary.h" +#include "VideoCommon/Assets/CustomTextureData.h" namespace VideoCommon { -class CustomTextureData; - // This class implements 'CustomAssetLibrary' and loads any assets // directly from the filesystem class DirectFilesystemAssetLibrary final : public CustomAssetLibrary @@ -35,7 +34,7 @@ public: private: // Loads additional mip levels into the texture structure until _mip texture is not found - bool LoadMips(const std::filesystem::path& asset_path, CustomTextureData* data); + bool LoadMips(const std::filesystem::path& asset_path, CustomTextureData::ArraySlice* data); // Gets the asset map given an asset id AssetMap GetAssetMapForID(const AssetID& asset_id) const; diff --git a/Source/Core/VideoCommon/Assets/TextureAsset.cpp b/Source/Core/VideoCommon/Assets/TextureAsset.cpp index 3bf477a58a..f7f3a00849 100644 --- a/Source/Core/VideoCommon/Assets/TextureAsset.cpp +++ b/Source/Core/VideoCommon/Assets/TextureAsset.cpp @@ -47,7 +47,7 @@ bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const return false; } - if (m_data->m_levels.empty()) + if (m_data->m_slices.empty()) { ERROR_LOG_FMT(VIDEO, "Game texture can't be validated for asset '{}' because no data was available.", @@ -55,9 +55,28 @@ bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const return false; } + if (m_data->m_slices.size() > 1) + { + ERROR_LOG_FMT( + VIDEO, + "Game texture can't be validated for asset '{}' because it has more slices than expected.", + GetAssetId()); + return false; + } + + const auto& slice = m_data->m_slices[0]; + if (slice.m_levels.empty()) + { + ERROR_LOG_FMT( + VIDEO, + "Game texture can't be validated for asset '{}' because first slice has no data available.", + GetAssetId()); + return false; + } + // Verify that the aspect ratio of the texture hasn't changed, as this could have // side-effects. - const VideoCommon::CustomTextureData::Level& first_mip = m_data->m_levels[0]; + const VideoCommon::CustomTextureData::ArraySlice::Level& first_mip = slice.m_levels[0]; if (first_mip.width * native_height != first_mip.height * native_width) { // Note: this feels like this should return an error but diff --git a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp index b774324a58..4d8df6a218 100644 --- a/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp +++ b/Source/Core/VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp @@ -423,15 +423,23 @@ void CustomPipelineAction::OnTextureCreate(GraphicsModActionData::TextureCreate* auto data = game_texture.m_asset->GetData(); if (data) { - if (create->texture_width != data->m_levels[0].width || - create->texture_height != data->m_levels[0].height) + if (data->m_slices.empty() || data->m_slices[0].m_levels.empty()) + { + ERROR_LOG_FMT( + VIDEO, + "Custom pipeline for texture '{}' has asset '{}' that does not have any texture data", + create->texture_name, game_texture.m_asset->GetAssetId()); + m_valid = false; + } + else if (create->texture_width != data->m_slices[0].m_levels[0].width || + create->texture_height != data->m_slices[0].m_levels[0].height) { ERROR_LOG_FMT(VIDEO, "Custom pipeline for texture '{}' has asset '{}' that does not match " "the width/height of the texture loaded. Texture {}x{} vs asset {}x{}", create->texture_name, game_texture.m_asset->GetAssetId(), - create->texture_width, create->texture_height, data->m_levels[0].width, - data->m_levels[0].height); + create->texture_width, create->texture_height, + data->m_slices[0].m_levels[0].width, data->m_slices[0].m_levels[0].height); m_valid = false; } } diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index 37dadba967..2e9bbd976a 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -1639,10 +1639,13 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp auto data = asset->GetData(); if (data) { - if (!data->m_levels.empty()) + if (!data->m_slices.empty()) { - height = data->m_levels[0].height; - width = data->m_levels[0].width; + if (!data->m_slices[0].m_levels.empty()) + { + height = data->m_slices[0].m_levels[0].height; + width = data->m_slices[0].m_levels[0].width; + } } } } @@ -1678,6 +1681,9 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp return entry; } +// Note: the following function assumes all CustomTextureData has a single slice. This is verified +// with the 'GameTexture::Validate' function after the data is loaded. Only a single slice is +// expected because each texture is loaded into a texture array RcTcacheEntry TextureCacheBase::CreateTextureEntry( const TextureCreationInfo& creation_info, const TextureInfo& texture_info, const int safety_color_sample_size, @@ -1696,12 +1702,12 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry( const auto calculate_max_levels = [&]() { const auto max_element = std::max_element( assets_data.begin(), assets_data.end(), [](const auto& lhs, const auto& rhs) { - return lhs->m_levels.size() < rhs->m_levels.size(); + return lhs->m_slices[0].m_levels.size() < rhs->m_slices[0].m_levels.size(); }); - return max_element->get()->m_levels.size(); + return max_element->get()->m_slices[0].m_levels.size(); }; const u32 texLevels = no_mips ? 1 : (u32)calculate_max_levels(); - const auto& first_level = assets_data[0]->m_levels[0]; + const auto& first_level = assets_data[0]->m_slices[0].m_levels[0]; const TextureConfig config(first_level.width, first_level.height, texLevels, static_cast(assets_data.size()), 1, first_level.format, 0); entry = AllocateCacheEntry(config); @@ -1710,11 +1716,12 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry( for (u32 data_index = 0; data_index < static_cast(assets_data.size()); data_index++) { const auto asset = assets_data[data_index]; + const auto& slice = asset->m_slices[0]; for (u32 level_index = 0; - level_index < std::min(texLevels, static_cast(asset->m_levels.size())); + level_index < std::min(texLevels, static_cast(slice.m_levels.size())); ++level_index) { - const auto& level = asset->m_levels[level_index]; + const auto& level = slice.m_levels[level_index]; entry->texture->Load(level_index, level.width, level.height, level.row_length, level.data.data(), level.data.size(), data_index); } From fa2bc535f10214963caf43068219da0fd3961dde Mon Sep 17 00:00:00 2001 From: Sketch <75850871+SketchMaster2001@users.noreply.github.com> Date: Thu, 31 Aug 2023 22:36:47 -0400 Subject: [PATCH 076/120] IOS/KD: Check if a file has an RSA signature --- Source/Core/Core/IOS/Network/KD/NWC24DL.cpp | 5 ++ Source/Core/Core/IOS/Network/KD/NWC24DL.h | 1 + .../Core/Core/IOS/Network/KD/NetKDRequest.cpp | 58 ++++++++++++------- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/Source/Core/Core/IOS/Network/KD/NWC24DL.cpp b/Source/Core/Core/IOS/Network/KD/NWC24DL.cpp index 2dac60819d..e61f3abbf1 100644 --- a/Source/Core/Core/IOS/Network/KD/NWC24DL.cpp +++ b/Source/Core/Core/IOS/Network/KD/NWC24DL.cpp @@ -120,6 +120,11 @@ bool NWC24Dl::IsEncrypted(u16 entry_index) const return !!Common::ExtractBit(Common::swap32(m_data.entries[entry_index].flags), 3); } +bool NWC24Dl::IsRSASigned(u16 entry_index) const +{ + return !Common::ExtractBit(Common::swap32(m_data.entries[entry_index].flags), 2); +} + u32 NWC24Dl::Magic() const { return Common::swap32(m_data.header.magic); diff --git a/Source/Core/Core/IOS/Network/KD/NWC24DL.h b/Source/Core/Core/IOS/Network/KD/NWC24DL.h index 1704d15223..e5427ea2ea 100644 --- a/Source/Core/Core/IOS/Network/KD/NWC24DL.h +++ b/Source/Core/Core/IOS/Network/KD/NWC24DL.h @@ -29,6 +29,7 @@ public: bool DoesEntryExist(u16 entry_index); bool IsEncrypted(u16 entry_index) const; + bool IsRSASigned(u16 entry_index) const; std::string GetVFFContentName(u16 entry_index, std::optional subtask_id) const; std::string GetDownloadURL(u16 entry_index, std::optional subtask_id) const; std::string GetVFFPath(u16 entry_index) const; diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp index b59c365155..856ce9860f 100644 --- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp +++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp @@ -255,32 +255,48 @@ NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index, return NWC24::WC24_ERR_SERVER; } - // Check if the filesize is smaller than the header size. - if (response->size() < sizeof(NWC24::WC24File)) + if (!m_dl_list.IsRSASigned(entry_index)) { - ERROR_LOG_FMT(IOS_WC24, "File at {} is too small to be a valid file.", url); - LogError(ErrorType::KD_Download, NWC24::WC24_ERR_BROKEN); - return NWC24::WC24_ERR_BROKEN; - } + // Data that is not signed with an RSA key will not have the WC24 header or 320 bytes before the + // actual data. We just have to make sure that the response is not empty. + if (response->empty()) + { + ERROR_LOG_FMT(IOS_WC24, "File at {} is empty.", url); + LogError(ErrorType::KD_Download, NWC24::WC24_ERR_BROKEN); + return NWC24::WC24_ERR_BROKEN; + } - // Now we read the file - NWC24::WC24File wc24File; - std::memcpy(&wc24File, response->data(), sizeof(NWC24::WC24File)); - - std::vector temp_buffer(response->begin() + 320, response->end()); - - if (m_dl_list.IsEncrypted(entry_index)) - { - NWC24::WC24PubkMod pubkMod = m_dl_list.GetWC24PubkMod(entry_index); - - file_data = std::vector(response->size() - 320); - - Common::AES::CryptOFB(pubkMod.aes_key, wc24File.iv, wc24File.iv, temp_buffer.data(), - file_data.data(), temp_buffer.size()); + file_data = *response; } else { - file_data = std::move(temp_buffer); + // Check if the filesize is smaller than the header size. + if (response->size() < sizeof(NWC24::WC24File)) + { + ERROR_LOG_FMT(IOS_WC24, "File at {} is too small to be a valid file.", url); + LogError(ErrorType::KD_Download, NWC24::WC24_ERR_BROKEN); + return NWC24::WC24_ERR_BROKEN; + } + + // Now we read the file + NWC24::WC24File wc24_file; + std::memcpy(&wc24_file, response->data(), sizeof(NWC24::WC24File)); + + std::vector temp_buffer(response->begin() + 320, response->end()); + + if (m_dl_list.IsEncrypted(entry_index)) + { + NWC24::WC24PubkMod pubk_mod = m_dl_list.GetWC24PubkMod(entry_index); + + file_data = std::vector(response->size() - 320); + + Common::AES::CryptOFB(pubk_mod.aes_key, wc24_file.iv, wc24_file.iv, temp_buffer.data(), + file_data.data(), temp_buffer.size()); + } + else + { + file_data = std::move(temp_buffer); + } } NWC24::ErrorCode reply = IOS::HLE::NWC24::OpenVFF(m_dl_list.GetVFFPath(entry_index), content_name, From c378365324d73880ab99895cdd2b0fa39dafe8ea Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 2 Sep 2023 11:37:27 +0200 Subject: [PATCH 077/120] Android: Get rid of unnecessary nullability in features.settings --- .../input/model/view/InputDeviceSetting.kt | 2 +- .../features/settings/model/NativeConfig.kt | 2 +- .../features/settings/model/StringSetting.kt | 2 +- .../settings/model/view/FloatSliderSetting.kt | 10 +++---- .../settings/model/view/HeaderSetting.kt | 2 +- .../settings/model/view/IntSliderSetting.kt | 6 ++-- .../model/view/InvertedSwitchSetting.kt | 4 +-- .../settings/model/view/LogSwitchSetting.kt | 4 +-- .../model/view/PercentSliderSetting.kt | 6 ++-- .../settings/model/view/SettingsItem.kt | 4 +-- .../model/view/SingleChoiceSetting.kt | 4 +-- .../settings/model/view/SliderSetting.kt | 8 ++--- .../model/view/StringSingleChoiceSetting.kt | 30 ++++++++----------- .../settings/model/view/SwitchSetting.kt | 10 +++---- .../features/settings/ui/SettingsAdapter.kt | 20 ++++++------- .../features/settings/ui/SettingsFragment.kt | 4 +-- .../ui/viewholder/FilePickerViewHolder.kt | 4 +-- .../viewholder/HeaderHyperLinkViewHolder.kt | 2 +- .../ui/viewholder/HeaderViewHolder.kt | 4 +-- .../ui/viewholder/RunRunnableViewHolder.kt | 5 ++-- .../ui/viewholder/SingleChoiceViewHolder.kt | 2 +- .../ui/viewholder/SliderViewHolder.kt | 5 ++-- 22 files changed, 69 insertions(+), 71 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.kt index c98af542c7..4ca29d9f33 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/input/model/view/InputDeviceSetting.kt @@ -13,7 +13,7 @@ class InputDeviceSetting( titleId: Int, descriptionId: Int, private val controller: EmulatedController -) : StringSingleChoiceSetting(context, null, titleId, descriptionId, null, null, null) { +) : StringSingleChoiceSetting(context, null, titleId, descriptionId, arrayOf(), arrayOf(), null) { init { refreshChoicesAndValues() } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/NativeConfig.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/NativeConfig.kt index c49667574e..5a2b31ce49 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/NativeConfig.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/NativeConfig.kt @@ -75,7 +75,7 @@ object NativeConfig { file: String, section: String, key: String, - value: String? + value: String ) @JvmStatic diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt index 7833e83542..bea6521229 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt @@ -94,7 +94,7 @@ enum class StringSetting( NativeConfig.setString(settings.writeLayer, file, section, key, newValue) } - fun setString(layer: Int, newValue: String?) { + fun setString(layer: Int, newValue: String) { NativeConfig.setString(layer, file, section, key, newValue) } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt index ee07c64056..34d0b0eb00 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/FloatSliderSetting.kt @@ -26,7 +26,7 @@ open class FloatSliderSetting : SliderSetting { descriptionId: Int, min: Float, max: Float, - units: String?, + units: String, stepSize: Float, showDecimal: Boolean ) : super(context, titleId, descriptionId, units, showDecimal) { @@ -39,10 +39,10 @@ open class FloatSliderSetting : SliderSetting { constructor( setting: AbstractFloatSetting, name: CharSequence, - description: CharSequence?, + description: CharSequence, min: Float, max: Float, - units: String?, + units: String, stepSize: Float, showDecimal: Boolean ) : super(name, description, units, showDecimal) { @@ -55,9 +55,9 @@ open class FloatSliderSetting : SliderSetting { open val selectedValue: Float get() = floatSetting.float - open fun setSelectedValue(settings: Settings?, selection: Float) { + open fun setSelectedValue(settings: Settings, selection: Float) { floatSetting.setFloat( - settings!!, + settings, BigDecimal((selection).toDouble()).round(MathContext(3)).toFloat() ) } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.kt index 3a166ddddf..57c2aeb49f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/HeaderSetting.kt @@ -14,7 +14,7 @@ open class HeaderSetting : SettingsItem { descriptionId: Int ) : super(context, titleId, descriptionId) - constructor(title: CharSequence, description: CharSequence?) : super(title, description) + constructor(title: CharSequence, description: CharSequence) : super(title, description) override val type: Int = TYPE_HEADER } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt index 45f2a71e69..a03af5dd6a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/IntSliderSetting.kt @@ -14,7 +14,7 @@ class IntSliderSetting( descriptionId: Int, val min: Int, val max: Int, - units: String?, + units: String, val stepSize: Int ) : SliderSetting(context, titleId, descriptionId, units, false) { @@ -24,7 +24,7 @@ class IntSliderSetting( val selectedValue: Int get() = intSetting.int - fun setSelectedValue(settings: Settings?, selection: Int) { - intSetting.setInt(settings!!, selection) + fun setSelectedValue(settings: Settings, selection: Int) { + intSetting.setInt(settings, selection) } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/InvertedSwitchSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/InvertedSwitchSetting.kt index 1a423c62f5..903ee39597 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/InvertedSwitchSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/InvertedSwitchSetting.kt @@ -19,7 +19,7 @@ class InvertedSwitchSetting( override val isChecked: Boolean get() = !booleanSetting.boolean - override fun setChecked(settings: Settings?, checked: Boolean) { - booleanSetting.setBoolean(settings!!, !checked) + override fun setChecked(settings: Settings, checked: Boolean) { + booleanSetting.setBoolean(settings, !checked) } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/LogSwitchSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/LogSwitchSetting.kt index 1bad7bff9f..830adb5353 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/LogSwitchSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/LogSwitchSetting.kt @@ -7,8 +7,8 @@ import org.dolphinemu.dolphinemu.features.settings.model.Settings class LogSwitchSetting( var key: String, - title: CharSequence?, - description: CharSequence? + title: CharSequence, + description: CharSequence ) : SwitchSetting( AdHocBooleanSetting( Settings.FILE_LOGGER, diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/PercentSliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/PercentSliderSetting.kt index 165bdebdc8..88b3667e4c 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/PercentSliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/PercentSliderSetting.kt @@ -15,7 +15,7 @@ class PercentSliderSetting( descriptionId: Int, min: Float, max: Float, - units: String?, + units: String, stepSize: Float, showDecimal: Boolean ) : FloatSliderSetting( @@ -32,9 +32,9 @@ class PercentSliderSetting( override val selectedValue: Float get() = (floatSetting.float * 100) - override fun setSelectedValue(settings: Settings?, selection: Float) { + override fun setSelectedValue(settings: Settings, selection: Float) { floatSetting.setFloat( - settings!!, + settings, BigDecimal((selection / 100).toDouble()).round(MathContext(3)).toFloat() ) } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.kt index 2180cbe029..3209400ff6 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SettingsItem.kt @@ -14,7 +14,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.Settings */ abstract class SettingsItem { val name: CharSequence - val description: CharSequence? + val description: CharSequence /** * Base constructor. @@ -22,7 +22,7 @@ abstract class SettingsItem { * @param name A text string to be displayed as this Setting's name. * @param description A text string to be displayed as this Setting's description. */ - constructor(name: CharSequence, description: CharSequence?) { + constructor(name: CharSequence, description: CharSequence) { this.name = name this.description = description } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SingleChoiceSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SingleChoiceSetting.kt index cd9f3ddb64..4744e9f351 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SingleChoiceSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SingleChoiceSetting.kt @@ -24,7 +24,7 @@ class SingleChoiceSetting( val selectedValue: Int get() = intSetting.int - fun setSelectedValue(settings: Settings?, selection: Int) { - intSetting.setInt(settings!!, selection) + fun setSelectedValue(settings: Settings, selection: Int) { + intSetting.setInt(settings, selection) } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt index 83b047f7a5..97703b269c 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SliderSetting.kt @@ -7,14 +7,14 @@ import android.content.Context sealed class SliderSetting : SettingsItem { override val type: Int = TYPE_SLIDER - val units: String? + val units: String val showDecimal: Boolean constructor( context: Context, nameId: Int, descriptionId: Int, - units: String?, + units: String, showDecimal: Boolean ) : super(context, nameId, descriptionId) { this.units = units @@ -23,8 +23,8 @@ sealed class SliderSetting : SettingsItem { constructor( name: CharSequence, - description: CharSequence?, - units: String?, + description: CharSequence, + units: String, showDecimal: Boolean ) : super(name, description) { this.units = units diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.kt index a67992ce2b..003dc22a08 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/StringSingleChoiceSetting.kt @@ -17,15 +17,15 @@ open class StringSingleChoiceSetting : SettingsItem { override val setting: AbstractSetting? get() = stringSetting - var choices: Array? + var choices: Array protected set - var values: Array? + var values: Array protected set val menuTag: MenuTag? var noChoicesAvailableString = 0 private set - open val selectedChoice: String? + open val selectedChoice: String get() = getChoiceAt(selectedValueIndex) open val selectedValue: String @@ -37,8 +37,8 @@ open class StringSingleChoiceSetting : SettingsItem { setting: AbstractStringSetting?, titleId: Int, descriptionId: Int, - choices: Array?, - values: Array?, + choices: Array, + values: Array, menuTag: MenuTag? = null ) : super(context, titleId, descriptionId) { stringSetting = setting @@ -75,27 +75,23 @@ open class StringSingleChoiceSetting : SettingsItem { this.menuTag = menuTag } - fun getChoiceAt(index: Int): String? { - if (choices == null) return null - - return if (index >= 0 && index < choices!!.size) { - choices!![index] + fun getChoiceAt(index: Int): String { + return if (index >= 0 && index < choices.size) { + choices[index] } else "" } - fun getValueAt(index: Int): String? { - if (values == null) return null - - return if (index >= 0 && index < values!!.size) { - values!![index] + fun getValueAt(index: Int): String { + return if (index >= 0 && index < values.size) { + values[index] } else "" } val selectedValueIndex: Int get() { val selectedValue = selectedValue - for (i in values!!.indices) { - if (values!![i] == selectedValue) { + for (i in values.indices) { + if (values[i] == selectedValue) { return i } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SwitchSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SwitchSetting.kt index 5f9d0cb218..6df17c201b 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SwitchSetting.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/view/SwitchSetting.kt @@ -26,16 +26,16 @@ open class SwitchSetting : SettingsItem { constructor( setting: AbstractBooleanSetting, - title: CharSequence?, - description: CharSequence? - ) : super(title!!, description) { + title: CharSequence, + description: CharSequence + ) : super(title, description) { booleanSetting = setting } open val isChecked: Boolean get() = booleanSetting.boolean - open fun setChecked(settings: Settings?, checked: Boolean) { - booleanSetting.setBoolean(settings!!, checked) + open fun setChecked(settings: Settings, checked: Boolean) { + booleanSetting.setBoolean(settings, checked) } } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt index b76d152d60..0d2b8786b0 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.kt @@ -138,7 +138,7 @@ class SettingsAdapter( return getItem(position).type } - fun setSettings(settings: ArrayList?) { + fun setSettings(settings: ArrayList) { settingsList = settings notifyDataSetChanged() } @@ -154,7 +154,7 @@ class SettingsAdapter( } fun onBooleanClick(item: SwitchSetting, checked: Boolean) { - item.setChecked(settings, checked) + item.setChecked(settings!!, checked) fragmentView.onSettingChanged() } @@ -194,7 +194,7 @@ class SettingsAdapter( item.refreshChoicesAndValues() val choices = item.choices val noChoicesAvailableString = item.noChoicesAvailableString - dialog = if (noChoicesAvailableString != 0 && choices!!.isEmpty()) { + dialog = if (noChoicesAvailableString != 0 && choices.isEmpty()) { MaterialAlertDialogBuilder(fragmentView.fragmentActivity) .setTitle(item.name) .setMessage(noChoicesAvailableString) @@ -355,7 +355,7 @@ class SettingsAdapter( dialog.show() } - fun onFilePickerDirectoryClick(item: SettingsItem?, position: Int) { + fun onFilePickerDirectoryClick(item: SettingsItem, position: Int) { clickedItem = item clickedPosition = position @@ -442,9 +442,9 @@ class SettingsAdapter( } fun onFilePickerConfirmation(selectedFile: String) { - val filePicker = clickedItem as FilePicker? + val filePicker = clickedItem as FilePicker - if (filePicker!!.getSelectedValue() != selectedFile) { + if (filePicker.getSelectedValue() != selectedFile) { notifyItemChanged(clickedPosition) fragmentView.onSettingChanged() } @@ -470,7 +470,7 @@ class SettingsAdapter( val value = getValueForSingleChoiceSelection(scSetting, which) if (scSetting.selectedValue != value) fragmentView.onSettingChanged() - scSetting.setSelectedValue(settings, value) + scSetting.setSelectedValue(settings!!, value) closeDialog() } @@ -490,7 +490,7 @@ class SettingsAdapter( val value = scSetting.getValueAt(which) if (scSetting.selectedValue != value) fragmentView.onSettingChanged() - scSetting.setSelectedValue(settings!!, value!!) + scSetting.setSelectedValue(settings!!, value) closeDialog() } @@ -499,7 +499,7 @@ class SettingsAdapter( if (sliderSetting.selectedValue != seekbarProgress.toInt()) { fragmentView.onSettingChanged() } - sliderSetting.setSelectedValue(settings, seekbarProgress.toInt()) + sliderSetting.setSelectedValue(settings!!, seekbarProgress.toInt()) closeDialog() } is FloatSliderSetting -> { @@ -507,7 +507,7 @@ class SettingsAdapter( if (sliderSetting.selectedValue != seekbarProgress) fragmentView.onSettingChanged() - sliderSetting.setSelectedValue(settings, seekbarProgress) + sliderSetting.setSelectedValue(settings!!, seekbarProgress) closeDialog() } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt index 8101d9efbf..651a86db51 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt @@ -90,8 +90,8 @@ class SettingsFragment : Fragment(), SettingsFragmentView { setInsets() - val activity = activity as SettingsActivityView? - presenter.onViewCreated(menuTag, activity!!.settings) + val activity = requireActivity() as SettingsActivityView + presenter.onViewCreated(menuTag, activity.settings) } override fun onDestroyView() { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/FilePickerViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/FilePickerViewHolder.kt index 215615828e..ff6246d292 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/FilePickerViewHolder.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/FilePickerViewHolder.kt @@ -15,8 +15,8 @@ import org.dolphinemu.dolphinemu.utils.FileBrowserHelper class FilePickerViewHolder( private val binding: ListItemSettingBinding, - adapter: SettingsAdapter? -) : SettingViewHolder(binding.getRoot(), adapter!!) { + adapter: SettingsAdapter +) : SettingViewHolder(binding.getRoot(), adapter) { lateinit var setting: FilePicker override val item: SettingsItem diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/HeaderHyperLinkViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/HeaderHyperLinkViewHolder.kt index 5bb58cf489..ab1caafebb 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/HeaderHyperLinkViewHolder.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/HeaderHyperLinkViewHolder.kt @@ -11,7 +11,7 @@ import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter class HeaderHyperLinkViewHolder( private val binding: ListItemHeaderBinding, - adapter: SettingsAdapter? + adapter: SettingsAdapter ) : HeaderViewHolder(binding, adapter) { init { itemView.setOnClickListener(null) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/HeaderViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/HeaderViewHolder.kt index 0f28bf1ffe..b1ca115711 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/HeaderViewHolder.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/HeaderViewHolder.kt @@ -9,8 +9,8 @@ import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter open class HeaderViewHolder( private val binding: ListItemHeaderBinding, - adapter: SettingsAdapter? -) : SettingViewHolder(binding.root, adapter!!) { + adapter: SettingsAdapter +) : SettingViewHolder(binding.root, adapter) { override val item: SettingsItem? = null init { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RunRunnableViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RunRunnableViewHolder.kt index 2ff39d51d5..a3b6e6dcc1 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RunRunnableViewHolder.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/RunRunnableViewHolder.kt @@ -14,9 +14,10 @@ import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter class RunRunnableViewHolder( - private val mBinding: ListItemSettingBinding, adapter: SettingsAdapter?, + private val mBinding: ListItemSettingBinding, + adapter: SettingsAdapter, private val mContext: Context -) : SettingViewHolder(mBinding.getRoot(), adapter!!) { +) : SettingViewHolder(mBinding.getRoot(), adapter) { private lateinit var setting: RunRunnable override val item: SettingsItem diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt index dabc51a986..4e66555f95 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SingleChoiceViewHolder.kt @@ -73,7 +73,7 @@ class SingleChoiceViewHolder( } override fun onClick(clicked: View) { - if (!item?.isEditable!!) { + if (!item!!.isEditable) { showNotRuntimeEditableError() return } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt index 0ec51430c7..27665f667a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SliderViewHolder.kt @@ -14,9 +14,10 @@ import org.dolphinemu.dolphinemu.features.settings.model.view.SliderSetting import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter class SliderViewHolder( - private val binding: ListItemSettingBinding, adapter: SettingsAdapter?, + private val binding: ListItemSettingBinding, + adapter: SettingsAdapter, private val context: Context -) : SettingViewHolder(binding.root, adapter!!) { +) : SettingViewHolder(binding.root, adapter) { private lateinit var setting: SliderSetting override val item: SettingsItem From 2154941c2c30e6a728c4212a17d492da439e922e Mon Sep 17 00:00:00 2001 From: Sketch <75850871+SketchMaster2001@users.noreply.github.com> Date: Mon, 14 Aug 2023 21:02:57 -0400 Subject: [PATCH 078/120] IOS/KD: Implement NWC24_CHECK_MAIL_NOW --- Source/Core/Common/CMakeLists.txt | 2 + Source/Core/Common/Crypto/HMAC.cpp | 27 ++ Source/Core/Common/Crypto/HMAC.h | 14 ++ Source/Core/Common/HttpRequest.cpp | 37 +++ Source/Core/Common/HttpRequest.h | 1 + Source/Core/Common/StringUtil.cpp | 5 + Source/Core/Common/StringUtil.h | 2 + Source/Core/Core/CMakeLists.txt | 3 + .../Core/IOS/Network/KD/Mail/MailCommon.h | 74 ++++++ .../Core/IOS/Network/KD/Mail/WC24Send.cpp | 68 +++++ .../Core/Core/IOS/Network/KD/Mail/WC24Send.h | 52 ++++ .../Core/Core/IOS/Network/KD/NWC24Config.cpp | 12 + Source/Core/Core/IOS/Network/KD/NWC24Config.h | 6 +- .../Core/Core/IOS/Network/KD/NetKDRequest.cpp | 237 ++++++++++++++++-- .../Core/Core/IOS/Network/KD/NetKDRequest.h | 42 +++- Source/Core/DolphinLib.props | 5 + 16 files changed, 569 insertions(+), 18 deletions(-) create mode 100644 Source/Core/Common/Crypto/HMAC.cpp create mode 100644 Source/Core/Common/Crypto/HMAC.h create mode 100644 Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h create mode 100644 Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp create mode 100644 Source/Core/Core/IOS/Network/KD/Mail/WC24Send.h diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 5f3194e09d..9288f1556c 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -29,6 +29,8 @@ add_library(common Crypto/bn.h Crypto/ec.cpp Crypto/ec.h + Crypto/HMAC.cpp + Crypto/HMAC.h Crypto/SHA1.cpp Crypto/SHA1.h Debug/MemoryPatches.cpp diff --git a/Source/Core/Common/Crypto/HMAC.cpp b/Source/Core/Common/Crypto/HMAC.cpp new file mode 100644 index 0000000000..660817ea75 --- /dev/null +++ b/Source/Core/Common/Crypto/HMAC.cpp @@ -0,0 +1,27 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "Common/Crypto/HMAC.h" +#include "Common/ScopeGuard.h" + +namespace Common::HMAC +{ +bool HMACWithSHA1(std::span key, std::span msg, u8* out) +{ + mbedtls_md_context_t ctx; + Common::ScopeGuard guard{[&ctx] { mbedtls_md_free(&ctx); }}; + mbedtls_md_init(&ctx); + if (mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), 1)) + return false; + + if (mbedtls_md_hmac_starts(&ctx, key.data(), key.size()) || + mbedtls_md_hmac_update(&ctx, msg.data(), msg.size()) || mbedtls_md_hmac_finish(&ctx, out)) + { + return false; + } + + return true; +} +} // namespace Common::HMAC diff --git a/Source/Core/Common/Crypto/HMAC.h b/Source/Core/Common/Crypto/HMAC.h new file mode 100644 index 0000000000..e870a2c98e --- /dev/null +++ b/Source/Core/Common/Crypto/HMAC.h @@ -0,0 +1,14 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/CommonTypes.h" + +#include + +namespace Common::HMAC +{ +// HMAC with the SHA1 message digest. Excepted output length is 20 bytes. +bool HMACWithSHA1(std::span key, std::span msg, u8* out); +} // namespace Common::HMAC diff --git a/Source/Core/Common/HttpRequest.cpp b/Source/Core/Common/HttpRequest.cpp index b3dfb0dc0c..3dc0f1d328 100644 --- a/Source/Core/Common/HttpRequest.cpp +++ b/Source/Core/Common/HttpRequest.cpp @@ -27,6 +27,7 @@ public: explicit Impl(std::chrono::milliseconds timeout_ms, ProgressCallback callback); bool IsValid() const; + std::string GetHeaderValue(std::string_view name) const; void SetCookies(const std::string& cookies); void UseIPv4(); void FollowRedirects(long max); @@ -41,6 +42,7 @@ public: private: static inline std::once_flag s_curl_was_initialized; ProgressCallback m_callback; + Headers m_response_headers; std::unique_ptr m_curl{nullptr, curl_easy_cleanup}; std::string m_error_string; }; @@ -82,6 +84,11 @@ s32 HttpRequest::GetLastResponseCode() const return m_impl->GetLastResponseCode(); } +std::string HttpRequest::GetHeaderValue(std::string_view name) const +{ + return m_impl->GetHeaderValue(name); +} + HttpRequest::Response HttpRequest::Get(const std::string& url, const Headers& headers, AllowedReturnCodes codes) { @@ -173,6 +180,17 @@ void HttpRequest::Impl::FollowRedirects(long max) curl_easy_setopt(m_curl.get(), CURLOPT_MAXREDIRS, max); } +std::string HttpRequest::Impl::GetHeaderValue(std::string_view name) const +{ + for (const auto& [key, value] : m_response_headers) + { + if (key == name) + return value.value(); + } + + return {}; +} + std::string HttpRequest::Impl::EscapeComponent(const std::string& string) { char* escaped = curl_easy_escape(m_curl.get(), string.c_str(), static_cast(string.size())); @@ -190,10 +208,26 @@ static size_t CurlWriteCallback(char* data, size_t size, size_t nmemb, void* use return actual_size; } +static size_t header_callback(char* buffer, size_t size, size_t nitems, void* userdata) +{ + auto* headers = static_cast(userdata); + std::string_view full_buffer = std::string_view{buffer, nitems}; + const size_t colon_pos = full_buffer.find(':'); + if (colon_pos == std::string::npos) + return nitems * size; + + const std::string_view key = full_buffer.substr(0, colon_pos); + const std::string_view value = StripWhitespace(full_buffer.substr(colon_pos + 1)); + + headers->emplace(std::string{key}, std::string{value}); + return nitems * size; +} + HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method method, const Headers& headers, const u8* payload, size_t size, AllowedReturnCodes codes) { + m_response_headers.clear(); curl_easy_setopt(m_curl.get(), CURLOPT_POST, method == Method::POST); curl_easy_setopt(m_curl.get(), CURLOPT_URL, url.c_str()); if (method == Method::POST) @@ -215,6 +249,9 @@ HttpRequest::Response HttpRequest::Impl::Fetch(const std::string& url, Method me } curl_easy_setopt(m_curl.get(), CURLOPT_HTTPHEADER, list); + curl_easy_setopt(m_curl.get(), CURLOPT_HEADERFUNCTION, header_callback); + curl_easy_setopt(m_curl.get(), CURLOPT_HEADERDATA, static_cast(&m_response_headers)); + std::vector buffer; curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, CurlWriteCallback); curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, &buffer); diff --git a/Source/Core/Common/HttpRequest.h b/Source/Core/Common/HttpRequest.h index 0b8afb1b1b..e1c6c40539 100644 --- a/Source/Core/Common/HttpRequest.h +++ b/Source/Core/Common/HttpRequest.h @@ -40,6 +40,7 @@ public: void FollowRedirects(long max = 1); s32 GetLastResponseCode() const; std::string EscapeComponent(const std::string& string); + std::string GetHeaderValue(std::string_view name) const; Response Get(const std::string& url, const Headers& headers = {}, AllowedReturnCodes codes = AllowedReturnCodes::Ok_Only); Response Post(const std::string& url, const std::vector& payload, const Headers& headers = {}, diff --git a/Source/Core/Common/StringUtil.cpp b/Source/Core/Common/StringUtil.cpp index 033bc66407..c9100ff11a 100644 --- a/Source/Core/Common/StringUtil.cpp +++ b/Source/Core/Common/StringUtil.cpp @@ -693,4 +693,9 @@ bool CaseInsensitiveEquals(std::string_view a, std::string_view b) return std::equal(a.begin(), a.end(), b.begin(), [](char ca, char cb) { return Common::ToLower(ca) == Common::ToLower(cb); }); } + +std::string BytesToHexString(std::span bytes) +{ + return fmt::format("{:02x}", fmt::join(bytes, "")); +} } // namespace Common diff --git a/Source/Core/Common/StringUtil.h b/Source/Core/Common/StringUtil.h index 1997fd0c5c..1699d9fa2f 100644 --- a/Source/Core/Common/StringUtil.h +++ b/Source/Core/Common/StringUtil.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -313,4 +314,5 @@ std::string GetEscapedHtml(std::string html); void ToLower(std::string* str); void ToUpper(std::string* str); bool CaseInsensitiveEquals(std::string_view a, std::string_view b); +std::string BytesToHexString(std::span bytes); } // namespace Common diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index f50185049a..c01e71bdf7 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -376,6 +376,9 @@ add_library(core IOS/Network/KD/VFF/VFFUtil.cpp IOS/Network/KD/VFF/VFFUtil.h IOS/Network/KD/WC24File.h + IOS/Network/KD/Mail/MailCommon.h + IOS/Network/KD/Mail/WC24Send.cpp + IOS/Network/KD/Mail/WC24Send.h IOS/Network/MACUtils.cpp IOS/Network/MACUtils.h IOS/Network/NCD/Manage.cpp diff --git a/Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h b/Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h new file mode 100644 index 0000000000..4bca377b89 --- /dev/null +++ b/Source/Core/Core/IOS/Network/KD/Mail/MailCommon.h @@ -0,0 +1,74 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/CommonTypes.h" + +#include + +namespace IOS::HLE::NWC24::Mail +{ +constexpr u32 MAIL_LIST_MAGIC = 0x57635466; // WcTf + +#pragma pack(push, 1) +struct MailListHeader final +{ + u32 magic; // 'WcTf' 0x57635466 + u32 version; // 4 in Wii Menu 4.x + u32 number_of_mail; + u32 total_entries; + u32 total_size_of_messages; + u32 filesize; + u32 next_entry_id; + u32 next_entry_offset; + u32 unk1; + u32 vff_free_space; + std::array unk2; + std::array mail_flag; +}; +static_assert(sizeof(MailListHeader) == 128); + +struct MultipartEntry final +{ + u32 offset; + u32 size; +}; + +struct MailListEntry final +{ + u32 id; + u32 flag; + u32 msg_size; + u32 app_id; + u32 header_length; + u32 tag; + u32 wii_cmd; + // Never validated + u32 crc32; + u64 from_friend_code; + u32 minutes_since_1900; + u32 padding; + u8 always_1; + u8 number_of_multipart_entries; + u16 app_group; + u32 packed_from; + u32 packed_to; + u32 packed_subject; + u32 packed_charset; + u32 packed_transfer_encoding; + u32 message_offset; + // Set to message_length if content transfer encoding is not base64. + u32 encoded_length; + std::array multipart_entries; + std::array multipart_sizes; + std::array multipart_content_types; + u32 message_length; + u32 dwc_id; + u32 always_0x80000000; + u32 padding3; +}; +static_assert(sizeof(MailListEntry) == 128); + +#pragma pack(pop) +} // namespace IOS::HLE::NWC24::Mail diff --git a/Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp b/Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp new file mode 100644 index 0000000000..f889266ec6 --- /dev/null +++ b/Source/Core/Core/IOS/Network/KD/Mail/WC24Send.cpp @@ -0,0 +1,68 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Core/IOS/Network/KD/Mail/WC24Send.h" +#include "Core/IOS/FS/FileSystem.h" +#include "Core/IOS/Uids.h" + +namespace IOS::HLE::NWC24::Mail +{ +constexpr const char SEND_LIST_PATH[] = "/" WII_WC24CONF_DIR "/mbox" + "/wc24send.ctl"; + +WC24SendList::WC24SendList(std::shared_ptr fs) : m_fs{std::move(fs)} +{ + ReadSendList(); +} + +void WC24SendList::ReadSendList() +{ + const auto file = m_fs->OpenFile(PID_KD, PID_KD, SEND_LIST_PATH, FS::Mode::Read); + if (!file || !file->Read(&m_data, 1)) + return; + + if (file->GetStatus()->size != SEND_LIST_SIZE) + { + ERROR_LOG_FMT(IOS_WC24, "The WC24 Send list file is not the correct size."); + return; + } + + const s32 file_error = CheckSendList(); + if (!file_error) + ERROR_LOG_FMT(IOS_WC24, "There is an error in the Send List for WC24 mail"); +} + +void WC24SendList::WriteSendList() const +{ + constexpr FS::Modes public_modes{FS::Mode::ReadWrite, FS::Mode::ReadWrite, FS::Mode::ReadWrite}; + m_fs->CreateFullPath(PID_KD, PID_KD, SEND_LIST_PATH, 0, public_modes); + const auto file = m_fs->CreateAndOpenFile(PID_KD, PID_KD, SEND_LIST_PATH, public_modes); + + if (!file || !file->Write(&m_data, 1)) + ERROR_LOG_FMT(IOS_WC24, "Failed to open or write WC24 Send list file"); +} + +bool WC24SendList::CheckSendList() const +{ + // 'WcTf' magic + if (Common::swap32(m_data.header.magic) != MAIL_LIST_MAGIC) + { + ERROR_LOG_FMT(IOS_WC24, "Send List magic mismatch ({} != {})", + Common::swap32(m_data.header.magic), MAIL_LIST_MAGIC); + return false; + } + + if (Common::swap32(m_data.header.version) != 4) + { + ERROR_LOG_FMT(IOS_WC24, "Send List version mismatch"); + return false; + } + + return true; +} + +std::string_view WC24SendList::GetMailFlag() const +{ + return {m_data.header.mail_flag.data(), m_data.header.mail_flag.size()}; +} +} // namespace IOS::HLE::NWC24::Mail diff --git a/Source/Core/Core/IOS/Network/KD/Mail/WC24Send.h b/Source/Core/Core/IOS/Network/KD/Mail/WC24Send.h new file mode 100644 index 0000000000..9f4e0399b1 --- /dev/null +++ b/Source/Core/Core/IOS/Network/KD/Mail/WC24Send.h @@ -0,0 +1,52 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "Common/CommonPaths.h" +#include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" +#include "Common/Swap.h" +#include "Core/IOS/Network/KD/Mail/MailCommon.h" +#include "Core/IOS/Network/KD/NWC24Config.h" + +namespace IOS::HLE +{ +namespace FS +{ +class FileSystem; +} +namespace NWC24::Mail +{ + +constexpr const char SEND_BOX_PATH[] = "/" WII_WC24CONF_DIR "/mbox" + "/wc24send.mbx"; +class WC24SendList final +{ +public: + explicit WC24SendList(std::shared_ptr fs); + + void ReadSendList(); + bool CheckSendList() const; + void WriteSendList() const; + + std::string_view GetMailFlag() const; + +private: + static constexpr u32 MAX_ENTRIES = 127; + static constexpr u32 SEND_LIST_SIZE = 16384; + +#pragma pack(push, 1) + struct SendList final + { + MailListHeader header; + std::array entries; + }; + static_assert(sizeof(SendList) == SEND_LIST_SIZE); +#pragma pack(pop) + + SendList m_data; + std::shared_ptr m_fs; +}; +} // namespace NWC24::Mail +} // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/Network/KD/NWC24Config.cpp b/Source/Core/Core/IOS/Network/KD/NWC24Config.cpp index 56b1d747cd..940a5360dd 100644 --- a/Source/Core/Core/IOS/Network/KD/NWC24Config.cpp +++ b/Source/Core/Core/IOS/Network/KD/NWC24Config.cpp @@ -216,4 +216,16 @@ void NWC24Config::SetEmail(const char* email) strncpy(m_data.email, email, MAX_EMAIL_LENGTH); m_data.email[MAX_EMAIL_LENGTH - 1] = '\0'; } + +std::string_view NWC24Config::GetMlchkid() const +{ + const size_t size = strnlen(m_data.mlchkid, MAX_MLCHKID_LENGTH); + return {m_data.mlchkid, size}; +} + +std::string NWC24Config::GetCheckURL() const +{ + const size_t size = strnlen(m_data.http_urls[1], MAX_URL_LENGTH); + return {m_data.http_urls[1], size}; +} } // namespace IOS::HLE::NWC24 diff --git a/Source/Core/Core/IOS/Network/KD/NWC24Config.h b/Source/Core/Core/IOS/Network/KD/NWC24Config.h index 0f3682449e..2b05d6dfd7 100644 --- a/Source/Core/Core/IOS/Network/KD/NWC24Config.h +++ b/Source/Core/Core/IOS/Network/KD/NWC24Config.h @@ -69,6 +69,9 @@ public: u32 Checksum() const; void SetChecksum(u32 checksum); + std::string_view GetMlchkid() const; + std::string GetCheckURL() const; + NWC24CreationStage CreationStage() const; void SetCreationStage(NWC24CreationStage creation_stage); @@ -92,6 +95,7 @@ private: MAX_URL_LENGTH = 0x80, MAX_EMAIL_LENGTH = 0x40, MAX_PASSWORD_LENGTH = 0x20, + MAX_MLCHKID_LENGTH = 0x24, }; #pragma pack(push, 1) @@ -104,7 +108,7 @@ private: NWC24CreationStage creation_stage; char email[MAX_EMAIL_LENGTH]; char paswd[MAX_PASSWORD_LENGTH]; - char mlchkid[0x24]; + char mlchkid[MAX_MLCHKID_LENGTH]; char http_urls[URL_COUNT][MAX_URL_LENGTH]; u8 reserved[0xDC]; u32 enable_booting; diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp index b59c365155..d28f1a3946 100644 --- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp +++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp @@ -12,11 +12,14 @@ #include "Common/BitUtils.h" #include "Common/CommonPaths.h" #include "Common/CommonTypes.h" +#include "Common/Crypto/HMAC.h" #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/NandPaths.h" #include "Common/SettingsHandler.h" +#include "Common/Random.h" +#include "Common/ScopeGuard.h" #include "Core/CommonTitles.h" #include "Core/HW/Memmap.h" #include "Core/IOS/FS/FileSystem.h" @@ -149,7 +152,8 @@ s32 NWC24MakeUserID(u64* nwc24_id, u32 hollywood_id, u16 id_ctr, HardwareModel h } // Anonymous namespace NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name) - : EmulationDevice(ios, device_name), config{ios.GetFS()}, m_dl_list{ios.GetFS()} + : EmulationDevice(ios, device_name), m_config{ios.GetFS()}, m_dl_list{ios.GetFS()}, + m_send_list{ios.GetFS()} { // Enable all NWC24 permissions m_scheduler_buffer[1] = Common::swap32(-1); @@ -161,6 +165,12 @@ NetKDRequestDevice::NetKDRequestDevice(EmulationKernel& ios, const std::string& m_async_replies.emplace(AsyncReply{task.request, reply.return_value}); } }); + + m_handle_mail = !ios.GetIOSC().IsUsingDefaultId(); + m_scheduler_work_queue.Reset("WiiConnect24 Scheduler Worker", + [](std::function task) { task(); }); + + m_scheduler_timer_thread = std::thread([this] { SchedulerTimer(); }); } NetKDRequestDevice::~NetKDRequestDevice() @@ -168,6 +178,16 @@ NetKDRequestDevice::~NetKDRequestDevice() auto socket_manager = GetEmulationKernel().GetSocketManager(); if (socket_manager) socket_manager->Clean(); + + { + std::lock_guard lg(m_scheduler_lock); + if (!m_scheduler_timer_thread.joinable()) + return; + + m_shutdown_event.Set(); + } + + m_scheduler_timer_thread.join(); } void NetKDRequestDevice::Update() @@ -183,6 +203,78 @@ void NetKDRequestDevice::Update() } } +void NetKDRequestDevice::SchedulerTimer() +{ + u32 mail_time_state = 0; + u32 download_time_state = 0; + Common::SetCurrentThreadName("KD Scheduler Timer"); + while (true) + { + { + std::lock_guard lg(m_scheduler_lock); + if (m_mail_span <= mail_time_state && m_handle_mail) + { + m_scheduler_work_queue.EmplaceItem([this] { SchedulerWorker(SchedulerEvent::Mail); }); + INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: Dispatching Mail Task from Scheduler"); + mail_time_state = 0; + } + + if (m_download_span <= download_time_state) + { + INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: Dispatching Download Task from Scheduler"); + m_scheduler_work_queue.EmplaceItem([this] { SchedulerWorker(SchedulerEvent::Download); }); + download_time_state = 0; + } + } + + if (m_shutdown_event.WaitFor(std::chrono::minutes{1})) + return; + + mail_time_state++; + download_time_state++; + } +} + +void NetKDRequestDevice::SchedulerWorker(const SchedulerEvent event) +{ + if (event == SchedulerEvent::Download) + { + // TODO: Implement downloader part of scheduler + return; + } + else + { + if (!m_config.IsRegistered()) + return; + + u32 mail_flag{}; + u32 interval{}; + + NWC24::ErrorCode code = KDCheckMail(&mail_flag, &interval); + if (code != NWC24::WC24_OK) + { + LogError(ErrorType::CheckMail, code); + } + } +} + +std::string NetKDRequestDevice::GetValueFromCGIResponse(const std::string& response, + const std::string& key) +{ + const std::vector raw_fields = SplitString(response, '\n'); + for (const std::string& field : raw_fields) + { + const std::vector key_value = SplitString(field, '='); + if (key_value.size() != 2) + continue; + + if (key_value[0] == key) + return std::string{StripWhitespace(key_value[1])}; + } + + return {}; +} + void NetKDRequestDevice::LogError(ErrorType error_type, s32 error_code) { s32 new_code{}; @@ -200,6 +292,9 @@ void NetKDRequestDevice::LogError(ErrorType error_type, s32 error_code) case ErrorType::Server: new_code = -(117000 + error_code); break; + case ErrorType::CheckMail: + new_code = -(102200 - error_code); + break; } std::lock_guard lg(m_scheduler_buffer_lock); @@ -211,6 +306,100 @@ void NetKDRequestDevice::LogError(ErrorType error_type, s32 error_code) m_scheduler_buffer[2] = Common::swap32(new_code); } +NWC24::ErrorCode NetKDRequestDevice::KDCheckMail(u32* mail_flag, u32* interval) +{ + bool success = false; + Common::ScopeGuard state_guard([&] { + std::lock_guard lg(m_scheduler_buffer_lock); + if (success) + { + // m_scheduler_buffer[11] contains the amount of times we have checked for mail this IOS + // session. + m_scheduler_buffer[11] = Common::swap32(Common::swap32(m_scheduler_buffer[11]) + 1); + } + m_scheduler_buffer[4] = static_cast(CurrentFunction::None); + }); + + { + std::lock_guard lg(m_scheduler_buffer_lock); + m_scheduler_buffer[4] = Common::swap32(static_cast(CurrentFunction::Check)); + } + + u64 random_number{}; + Common::Random::Generate(&random_number, sizeof(u64)); + const std::string form_data( + fmt::format("mlchkid={}&chlng={}", m_config.GetMlchkid(), random_number)); + const Common::HttpRequest::Response response = m_http.Post(m_config.GetCheckURL(), form_data); + + if (!response) + { + ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_CHECK_MAIL_NOW: Failed to request data at {}.", + m_config.GetCheckURL()); + return NWC24::WC24_ERR_SERVER; + } + + const std::string response_str = {response->begin(), response->end()}; + const std::string code = GetValueFromCGIResponse(response_str, "cd"); + if (code != "100") + { + ERROR_LOG_FMT( + IOS_WC24, + "NET_KD_REQ: IOCTL_NWC24_CHECK_MAIL_NOW: Mail server returned non-success code: {}", code); + return NWC24::WC24_ERR_SERVER; + } + + const std::string server_hmac = GetValueFromCGIResponse(response_str, "res"); + const std::string str_mail_flag = GetValueFromCGIResponse(response_str, "mail.flag"); + const std::string str_interval = GetValueFromCGIResponse(response_str, "interval"); + DEBUG_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_CHECK_MAIL_NOW: Server HMAC: {}", server_hmac); + + // On a real Wii, a response to a challenge is expected and would be verified by KD. + const std::string hmac_message = + fmt::format("{}\nw{}\n{}\n{}", random_number, m_config.Id(), str_mail_flag, str_interval); + std::array hashed{}; + Common::HMAC::HMACWithSHA1( + MAIL_CHECK_KEY, + std::span(reinterpret_cast(hmac_message.data()), hmac_message.size()), + hashed.data()); + + // On a real Wii, strncmp is used to compare both hashes. This means that it is a case-sensitive + // comparison. KD will generate a lowercase hash as well as expect a lowercase hash from the + // server. + if (Common::BytesToHexString(hashed) != server_hmac) + { + ERROR_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_CHECK_MAIL_NOW: Server HMAC is invalid."); + return NWC24::WC24_ERR_SERVER; + } + + *mail_flag = std::strncmp(str_mail_flag.data(), m_send_list.GetMailFlag().data(), 22) != 0; + + { + std::lock_guard scheduler_lg(m_scheduler_lock); + bool did_parse = TryParse(m_http.GetHeaderValue("X-Wii-Mail-Check-Span"), interval); + if (did_parse) + { + if (*interval == 0) + { + *interval = 1; + } + + m_mail_span = *interval; + } + + did_parse = TryParse(m_http.GetHeaderValue("X-Wii-Download-Span"), &m_download_span); + if (did_parse) + { + if (m_download_span == 0) + { + m_download_span = 1; + } + } + } + + success = true; + return NWC24::WC24_OK; +} + NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index, const std::optional subtask_id) { @@ -292,6 +481,21 @@ NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index, return reply; } +IPCReply NetKDRequestDevice::HandleNWC24CheckMailNow(const IOCtlRequest& request) +{ + auto& system = GetSystem(); + auto& memory = system.GetMemory(); + + u32 mail_flag{}; + u32 interval{}; + const NWC24::ErrorCode reply = KDCheckMail(&mail_flag, &interval); + + WriteReturnValue(reply, request.buffer_out); + memory.Write_U32(mail_flag, request.buffer_out + 4); + memory.Write_U32(interval, request.buffer_out + 8); + return IPCReply(IPC_SUCCESS); +} + IPCReply NetKDRequestDevice::HandleNWC24DownloadNowEx(const IOCtlRequest& request) { m_dl_list.ReadDlList(); @@ -430,7 +634,7 @@ std::optional NetKDRequestDevice::IOCtl(const IOCtlRequest& request) case IOCTL_NWC24_REQUEST_GENERATED_USER_ID: // (Input: none, Output: 32 bytes) INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_REQUEST_GENERATED_USER_ID"); - if (config.IsCreated()) + if (m_config.IsCreated()) { const std::string settings_file_path = Common::GetTitleDataPath(Titles::SYSTEM_MENU) + "/" WII_SETTING; @@ -451,19 +655,19 @@ std::optional NetKDRequestDevice::IOCtl(const IOCtlRequest& request) if (!area.empty() && !model.empty()) { const u8 area_code = GetAreaCode(area); - const u8 id_ctr = u8(config.IdGen()); + const u8 id_ctr = u8(m_config.IdGen()); const HardwareModel hardware_model = GetHardwareModel(model); const u32 hollywood_id = m_ios.GetIOSC().GetDeviceId(); u64 user_id = 0; const s32 ret = NWC24MakeUserID(&user_id, hollywood_id, id_ctr, hardware_model, area_code); - config.SetId(user_id); - config.IncrementIdGen(); - config.SetCreationStage(NWC24::NWC24CreationStage::Generated); - config.SetChecksum(config.CalculateNwc24ConfigChecksum()); - config.WriteConfig(); - config.WriteCBK(); + m_config.SetId(user_id); + m_config.IncrementIdGen(); + m_config.SetCreationStage(NWC24::NWC24CreationStage::Generated); + m_config.SetChecksum(m_config.CalculateNwc24ConfigChecksum()); + m_config.WriteConfig(); + m_config.WriteCBK(); WriteReturnValue(ret, request.buffer_out); } @@ -473,16 +677,16 @@ std::optional NetKDRequestDevice::IOCtl(const IOCtlRequest& request) WriteReturnValue(NWC24::WC24_ERR_FATAL, request.buffer_out); } } - else if (config.IsGenerated()) + else if (m_config.IsGenerated()) { WriteReturnValue(NWC24::WC24_ERR_ID_GENERATED, request.buffer_out); } - else if (config.IsRegistered()) + else if (m_config.IsRegistered()) { WriteReturnValue(NWC24::WC24_ERR_ID_REGISTERED, request.buffer_out); } - memory.Write_U64(config.Id(), request.buffer_out + 4); - memory.Write_U32(u32(config.CreationStage()), request.buffer_out + 0xC); + memory.Write_U64(m_config.Id(), request.buffer_out + 4); + memory.Write_U32(u32(m_config.CreationStage()), request.buffer_out + 0xC); break; case IOCTL_NWC24_GET_SCHEDULER_STAT: @@ -498,8 +702,8 @@ std::optional NetKDRequestDevice::IOCtl(const IOCtlRequest& request) request.buffer_out_size); // On a real Wii, GetSchedulerStat copies memory containing a list of error codes recorded by - // KD among other things. In most instances there will never be more than one error code - // recorded as we do not have a scheduler. + // KD among other things. + std::lock_guard lg(m_scheduler_buffer_lock); const u32 out_size = std::min(request.buffer_out_size, 256U); memory.CopyToEmu(request.buffer_out, m_scheduler_buffer.data(), out_size); break; @@ -509,6 +713,9 @@ std::optional NetKDRequestDevice::IOCtl(const IOCtlRequest& request) INFO_LOG_FMT(IOS_WC24, "NET_KD_REQ: IOCTL_NWC24_SAVE_MAIL_NOW - NI"); break; + case IOCTL_NWC24_CHECK_MAIL_NOW: + return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24CheckMailNow, request); + case IOCTL_NWC24_DOWNLOAD_NOW_EX: return LaunchAsyncTask(&NetKDRequestDevice::HandleNWC24DownloadNowEx, request); diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h index 035b4a286b..4f7a780359 100644 --- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h +++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h @@ -9,9 +9,11 @@ #include "Common/CommonPaths.h" #include "Common/CommonTypes.h" +#include "Common/Event.h" #include "Common/HttpRequest.h" #include "Common/WorkQueueThread.h" #include "Core/IOS/Device.h" +#include "Core/IOS/Network/KD/Mail/WC24Send.h" #include "Core/IOS/Network/KD/NWC24Config.h" #include "Core/IOS/Network/KD/NWC24DL.h" @@ -26,6 +28,7 @@ public: NetKDRequestDevice(EmulationKernel& ios, const std::string& device_name); IPCReply HandleNWC24DownloadNowEx(const IOCtlRequest& request); NWC24::ErrorCode KDDownload(const u16 entry_index, const std::optional subtask_id); + IPCReply HandleNWC24CheckMailNow(const IOCtlRequest& request); ~NetKDRequestDevice() override; std::optional IOCtl(const IOCtlRequest& request) override; @@ -51,19 +54,48 @@ private: return std::nullopt; } + enum class CurrentFunction : u32 + { + None = 0, + Account = 1, + Check = 2, + Receive = 3, + Send = 5, + Save = 6, + Download = 7, + }; + enum class ErrorType { Account, KD_Download, Client, Server, + CheckMail, }; - void LogError(ErrorType error_type, s32 error_code); + enum class SchedulerEvent + { + Mail, + Download, + }; - NWC24::NWC24Config config; + NWC24::ErrorCode KDCheckMail(u32* mail_flag, u32* interval); + + void LogError(ErrorType error_type, s32 error_code); + void SchedulerTimer(); + void SchedulerWorker(SchedulerEvent event); + + static std::string GetValueFromCGIResponse(const std::string& response, const std::string& key); + static constexpr std::array MAIL_CHECK_KEY = {0xce, 0x4c, 0xf2, 0x9a, 0x3d, 0x6b, 0xe1, + 0xc2, 0x61, 0x91, 0x72, 0xb5, 0xcb, 0x29, + 0x8c, 0x89, 0x72, 0xd4, 0x50, 0xad}; + + NWC24::NWC24Config m_config; NWC24::NWC24Dl m_dl_list; + NWC24::Mail::WC24SendList m_send_list; Common::WorkQueueThread m_work_queue; + Common::WorkQueueThread> m_scheduler_work_queue; std::mutex m_async_reply_lock; std::mutex m_scheduler_buffer_lock; std::queue m_async_replies; @@ -71,5 +103,11 @@ private: std::array m_scheduler_buffer{}; // TODO: Maybe move away from Common::HttpRequest? Common::HttpRequest m_http{std::chrono::minutes{1}}; + u32 m_download_span = 2; + u32 m_mail_span = 1; + bool m_handle_mail; + Common::Event m_shutdown_event; + std::mutex m_scheduler_lock; + std::thread m_scheduler_timer_thread; }; } // namespace IOS::HLE diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 1b44f15ec4..be2c076148 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -35,6 +35,7 @@ + @@ -360,6 +361,8 @@ + + @@ -757,6 +760,7 @@ + @@ -999,6 +1003,7 @@ + From fdaa82f96ddfe7818207cc3930e683fe8ddff1bc Mon Sep 17 00:00:00 2001 From: Sketch <75850871+SketchMaster2001@users.noreply.github.com> Date: Sun, 3 Sep 2023 15:56:02 -0400 Subject: [PATCH 079/120] Add Everybody Votes Channel and Region Select to WiiLink config --- Data/Sys/GameSettings/HAJ.ini | 9 +++++++++ Data/Sys/GameSettings/HAL.ini | 19 +++++++++++++++++++ Source/Core/Core/CommonTitles.h | 6 ++++++ Source/Core/Core/WC24PatchEngine.cpp | 6 ++++-- 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 Data/Sys/GameSettings/HAJ.ini create mode 100644 Data/Sys/GameSettings/HAL.ini diff --git a/Data/Sys/GameSettings/HAJ.ini b/Data/Sys/GameSettings/HAJ.ini new file mode 100644 index 0000000000..97da4f2deb --- /dev/null +++ b/Data/Sys/GameSettings/HAJ.ini @@ -0,0 +1,9 @@ +# HAJE01, HAJJ01, HAPP01 - Everybody Votes Channel + +[WC24Patch] +$Main +vt.wapp.wii.com:vt.wiilink24.com:1 +vtp.wapp.wii.com:vtp.wiilink24.com:0 + +[WC24Patch_Enabled] +$Main diff --git a/Data/Sys/GameSettings/HAL.ini b/Data/Sys/GameSettings/HAL.ini new file mode 100644 index 0000000000..5d8cd65f8b --- /dev/null +++ b/Data/Sys/GameSettings/HAL.ini @@ -0,0 +1,19 @@ +# HALE01, HALJ01, HALP01 - Region Select + +[OnFrame_Enabled] +$RSAPatch + +[OnFrame] +# This patch changes the flag in its nwc24dl.bin to not have an RSA signature. +# Although Dolphin doesn't validate the RSA signature, a real Wii does which is why we added this workaround. +$RSAPatch +0x80009DEC:dword:0x60000000 +0x8001AB20:dword:0x38600001 +0x8001AC68:dword:0x38600001 + +[WC24Patch] +$Main +cfh.wapp.wii.com:ch.wiilink24.com:1 + +[WC24Patch_Enabled] +$Main diff --git a/Source/Core/Core/CommonTitles.h b/Source/Core/Core/CommonTitles.h index 50c8b4d742..a6043c7cd6 100644 --- a/Source/Core/Core/CommonTitles.h +++ b/Source/Core/Core/CommonTitles.h @@ -39,6 +39,12 @@ constexpr u64 EVERYBODY_VOTES_CHANNEL_NTSC_J = 0x0001000148414a4a; constexpr u64 EVERYBODY_VOTES_CHANNEL_PAL = 0x0001000148414a50; +constexpr u64 REGION_SELECT_CHANNEL_NTSC_U = 0x0001000848414c45; + +constexpr u64 REGION_SELECT_CHANNEL_NTSC_J = 0x0001000848414c4a; + +constexpr u64 REGION_SELECT_CHANNEL_PAL = 0x0001000848414c50; + constexpr u64 IOS(u32 major_version) { return 0x0000000100000000 | major_version; diff --git a/Source/Core/Core/WC24PatchEngine.cpp b/Source/Core/Core/WC24PatchEngine.cpp index b5092a7feb..aa62ac7580 100644 --- a/Source/Core/Core/WC24PatchEngine.cpp +++ b/Source/Core/Core/WC24PatchEngine.cpp @@ -18,8 +18,7 @@ namespace WC24PatchEngine { -static std::array s_wc24_channels{ - // Nintendo Channel +static constexpr std::array s_wc24_channels{ Titles::NINTENDO_CHANNEL_NTSC_U, Titles::NINTENDO_CHANNEL_NTSC_J, Titles::NINTENDO_CHANNEL_PAL, @@ -32,6 +31,9 @@ static std::array s_wc24_channels{ Titles::EVERYBODY_VOTES_CHANNEL_NTSC_U, Titles::EVERYBODY_VOTES_CHANNEL_NTSC_J, Titles::EVERYBODY_VOTES_CHANNEL_PAL, + Titles::REGION_SELECT_CHANNEL_NTSC_U, + Titles::REGION_SELECT_CHANNEL_NTSC_J, + Titles::REGION_SELECT_CHANNEL_PAL, }; static std::vector s_patches; From fb852a506245510fc6da8f05147c7b9876d36b2e Mon Sep 17 00:00:00 2001 From: Sketch <75850871+SketchMaster2001@users.noreply.github.com> Date: Sun, 3 Sep 2023 15:57:51 -0400 Subject: [PATCH 080/120] VideoCommon: Deinit Graphics Mod Manager implicitly --- Source/Core/VideoCommon/VideoBackendBase.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Core/VideoCommon/VideoBackendBase.cpp b/Source/Core/VideoCommon/VideoBackendBase.cpp index 6e466c8557..fbafb58c21 100644 --- a/Source/Core/VideoCommon/VideoBackendBase.cpp +++ b/Source/Core/VideoCommon/VideoBackendBase.cpp @@ -407,6 +407,7 @@ void VideoBackendBase::ShutdownShared() g_bounding_box.reset(); g_perf_query.reset(); + g_graphics_mod_manager.reset(); g_texture_cache.reset(); g_framebuffer_manager.reset(); g_shader_cache.reset(); From 348e60cd3f4a9d940fedf5d98dc02bb2a06d8973 Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 17 Jun 2023 14:13:38 -0400 Subject: [PATCH 081/120] Rearranged RetroAchievements startup process Moved AchievementManager Init further down in the MainWindow constructor; its original position was because it had an impact on the contents of the menu bar, and this is no longer the case. --- Source/Core/DolphinQt/MainWindow.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 5698b2acfa..5e9bc67896 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -42,6 +42,7 @@ #include "Core/Boot/Boot.h" #include "Core/BootManager.h" #include "Core/CommonTitles.h" +#include "Core/Config/AchievementSettings.h" #include "Core/Config/MainSettings.h" #include "Core/Config/NetplaySettings.h" #include "Core/Config/WiimoteSettings.h" @@ -227,11 +228,6 @@ MainWindow::MainWindow(std::unique_ptr boot_parameters, InitControllers(); -#ifdef USE_RETRO_ACHIEVEMENTS - // This has to be done before CreateComponents() so it's initialized. - AchievementManager::GetInstance()->Init(); -#endif // USE_RETRO_ACHIEVEMENTS - CreateComponents(); ConnectGameList(); @@ -256,6 +252,10 @@ MainWindow::MainWindow(std::unique_ptr boot_parameters, NetPlayInit(); +#ifdef USE_RETRO_ACHIEVEMENTS + AchievementManager::GetInstance()->Init(); +#endif // USE_RETRO_ACHIEVEMENTS + #if defined(__unix__) || defined(__unix) || defined(__APPLE__) auto* daemon = new SignalDaemon(this); From de781a6fa79f466f66b56f161e3e1a15289691b1 Mon Sep 17 00:00:00 2001 From: OatmealDome Date: Mon, 9 Jan 2023 00:25:11 -0500 Subject: [PATCH 082/120] RenderBase: Allow widescreen heuristic's transition threshold to be overridden by onion config --- Source/Core/Core/Config/GraphicsSettings.cpp | 2 ++ Source/Core/Core/Config/GraphicsSettings.h | 1 + Source/Core/VideoCommon/VideoConfig.cpp | 2 ++ Source/Core/VideoCommon/VideoConfig.h | 1 + Source/Core/VideoCommon/Widescreen.cpp | 10 +++++----- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp index ad0059ca6d..dc19c65817 100644 --- a/Source/Core/Core/Config/GraphicsSettings.cpp +++ b/Source/Core/Core/Config/GraphicsSettings.cpp @@ -23,6 +23,8 @@ const Info GFX_WIDESCREEN_HACK{{System::GFX, "Settings", "wideScreenHack"} const Info GFX_ASPECT_RATIO{{System::GFX, "Settings", "AspectRatio"}, AspectMode::Auto}; const Info GFX_SUGGESTED_ASPECT_RATIO{{System::GFX, "Settings", "SuggestedAspectRatio"}, AspectMode::Auto}; +const Info GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD{ + {System::GFX, "Settings", "WidescreenHeuristicTransitionThreshold"}, 3}; const Info GFX_CROP{{System::GFX, "Settings", "Crop"}, false}; const Info GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES{ {System::GFX, "Settings", "SafeTextureCacheColorSamples"}, 128}; diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h index 165114e419..98c4031cf5 100644 --- a/Source/Core/Core/Config/GraphicsSettings.h +++ b/Source/Core/Core/Config/GraphicsSettings.h @@ -29,6 +29,7 @@ extern const Info GFX_ADAPTER; extern const Info GFX_WIDESCREEN_HACK; extern const Info GFX_ASPECT_RATIO; extern const Info GFX_SUGGESTED_ASPECT_RATIO; +extern const Info GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD; extern const Info GFX_CROP; extern const Info GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES; extern const Info GFX_SHOW_FPS; diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index d9a35b727e..a5a6d77728 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -86,6 +86,8 @@ void VideoConfig::Refresh() bWidescreenHack = Config::Get(Config::GFX_WIDESCREEN_HACK); aspect_mode = Config::Get(Config::GFX_ASPECT_RATIO); suggested_aspect_mode = Config::Get(Config::GFX_SUGGESTED_ASPECT_RATIO); + widescreen_heuristic_transition_threshold = + Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD); bCrop = Config::Get(Config::GFX_CROP); iSafeTextureCache_ColorSamples = Config::Get(Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES); bShowFPS = Config::Get(Config::GFX_SHOW_FPS); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index 6e34a43934..4a6c1bb811 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -106,6 +106,7 @@ struct VideoConfig final bool bWidescreenHack = false; AspectMode aspect_mode{}; AspectMode suggested_aspect_mode{}; + u32 widescreen_heuristic_transition_threshold = 0; bool bCrop = false; // Aspect ratio controls. bool bShaderCache = false; diff --git a/Source/Core/VideoCommon/Widescreen.cpp b/Source/Core/VideoCommon/Widescreen.cpp index dd1393d309..4ca1ac3ccd 100644 --- a/Source/Core/VideoCommon/Widescreen.cpp +++ b/Source/Core/VideoCommon/Widescreen.cpp @@ -70,13 +70,13 @@ void WidescreenManager::UpdateWidescreenHeuristic() // Modify the threshold based on which aspect ratio we're already using: // If the game's in 4:3, it probably won't switch to anamorphic, and vice-versa. - static constexpr u32 TRANSITION_THRESHOLD = 3; + const u32 transition_threshold = g_ActiveConfig.widescreen_heuristic_transition_threshold; - const auto looks_normal = [](auto& counts) { - return counts.normal_vertex_count > counts.anamorphic_vertex_count * TRANSITION_THRESHOLD; + const auto looks_normal = [transition_threshold](auto& counts) { + return counts.normal_vertex_count > counts.anamorphic_vertex_count * transition_threshold; }; - const auto looks_anamorphic = [](auto& counts) { - return counts.anamorphic_vertex_count > counts.normal_vertex_count * TRANSITION_THRESHOLD; + const auto looks_anamorphic = [transition_threshold](auto& counts) { + return counts.anamorphic_vertex_count > counts.normal_vertex_count * transition_threshold; }; const auto& persp = flush_statistics.perspective; From 4938b99600da9fc6e1241e6aad476286b74850a9 Mon Sep 17 00:00:00 2001 From: OatmealDome Date: Mon, 4 Sep 2023 13:02:17 -0400 Subject: [PATCH 083/120] VertexManagerBase: Allow widecreen heuristic constants to be overriden by onion config --- Source/Core/Core/Config/GraphicsSettings.cpp | 4 ++++ Source/Core/Core/Config/GraphicsSettings.h | 2 ++ Source/Core/VideoCommon/VertexManagerBase.cpp | 23 +++++++++++-------- Source/Core/VideoCommon/VideoConfig.cpp | 4 ++++ Source/Core/VideoCommon/VideoConfig.h | 2 ++ 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp index dc19c65817..0aaab5ded2 100644 --- a/Source/Core/Core/Config/GraphicsSettings.cpp +++ b/Source/Core/Core/Config/GraphicsSettings.cpp @@ -25,6 +25,10 @@ const Info GFX_SUGGESTED_ASPECT_RATIO{{System::GFX, "Settings", "Sug AspectMode::Auto}; const Info GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD{ {System::GFX, "Settings", "WidescreenHeuristicTransitionThreshold"}, 3}; +const Info GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_IDEAL{ + {System::GFX, "Settings", "WidescreenHeuristicAspectRatioIdeal"}, (16 / 9.f) / (4 / 3.f)}; +const Info GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_SLOP{ + {System::GFX, "Settings", "WidescreenHeuristicAspectRatioSlop"}, 0.11f}; const Info GFX_CROP{{System::GFX, "Settings", "Crop"}, false}; const Info GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES{ {System::GFX, "Settings", "SafeTextureCacheColorSamples"}, 128}; diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h index 98c4031cf5..26e741dfde 100644 --- a/Source/Core/Core/Config/GraphicsSettings.h +++ b/Source/Core/Core/Config/GraphicsSettings.h @@ -30,6 +30,8 @@ extern const Info GFX_WIDESCREEN_HACK; extern const Info GFX_ASPECT_RATIO; extern const Info GFX_SUGGESTED_ASPECT_RATIO; extern const Info GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD; +extern const Info GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_IDEAL; +extern const Info GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_SLOP; extern const Info GFX_CROP; extern const Info GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES; extern const Info GFX_SHOW_FPS; diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index 564eb684dc..e1d294f331 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -76,26 +76,26 @@ constexpr Common::EnumMap primitive_fr // by ~9% in opposite directions. // Just in case any game decides to take this into account, we do both these // tests with a large amount of slop. -static constexpr float ASPECT_RATIO_SLOP = 0.11f; -static bool IsAnamorphicProjection(const Projection::Raw& projection, const Viewport& viewport) +static bool IsAnamorphicProjection(const Projection::Raw& projection, const Viewport& viewport, + const float ideal_ratio, const float slop) { // If ratio between our projection and viewport aspect ratios is similar to 16:9 / 4:3 - // we have an anamorphic projection. - static constexpr float IDEAL_RATIO = (16 / 9.f) / (4 / 3.f); + // (the "ideal ratio"), we have an anamorphic projection. This value can be overridden + // by a GameINI. const float projection_ar = projection[2] / projection[0]; const float viewport_ar = viewport.wd / viewport.ht; - return std::abs(std::abs(projection_ar / viewport_ar) - IDEAL_RATIO) < - IDEAL_RATIO * ASPECT_RATIO_SLOP; + return std::abs(std::abs(projection_ar / viewport_ar) - ideal_ratio) < ideal_ratio * slop; } -static bool IsNormalProjection(const Projection::Raw& projection, const Viewport& viewport) +static bool IsNormalProjection(const Projection::Raw& projection, const Viewport& viewport, + const float slop) { const float projection_ar = projection[2] / projection[0]; const float viewport_ar = viewport.wd / viewport.ht; - return std::abs(std::abs(projection_ar / viewport_ar) - 1) < ASPECT_RATIO_SLOP; + return std::abs(std::abs(projection_ar / viewport_ar) - 1) < slop; } VertexManagerBase::VertexManagerBase() @@ -507,12 +507,15 @@ void VertexManagerBase::Flush() auto& counts = is_perspective ? m_flush_statistics.perspective : m_flush_statistics.orthographic; - if (IsAnamorphicProjection(xfmem.projection.rawProjection, xfmem.viewport)) + const float ideal_ratio = g_ActiveConfig.widescreen_heuristic_aspect_ratio_ideal; + const float slop = g_ActiveConfig.widescreen_heuristic_aspect_ratio_slop; + + if (IsAnamorphicProjection(xfmem.projection.rawProjection, xfmem.viewport, ideal_ratio, slop)) { ++counts.anamorphic_flush_count; counts.anamorphic_vertex_count += m_index_generator.GetIndexLen(); } - else if (IsNormalProjection(xfmem.projection.rawProjection, xfmem.viewport)) + else if (IsNormalProjection(xfmem.projection.rawProjection, xfmem.viewport, slop)) { ++counts.normal_flush_count; counts.normal_vertex_count += m_index_generator.GetIndexLen(); diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index a5a6d77728..e2096dfd1f 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -88,6 +88,10 @@ void VideoConfig::Refresh() suggested_aspect_mode = Config::Get(Config::GFX_SUGGESTED_ASPECT_RATIO); widescreen_heuristic_transition_threshold = Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD); + widescreen_heuristic_aspect_ratio_ideal = + Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_IDEAL); + widescreen_heuristic_aspect_ratio_slop = + Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_SLOP); bCrop = Config::Get(Config::GFX_CROP); iSafeTextureCache_ColorSamples = Config::Get(Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES); bShowFPS = Config::Get(Config::GFX_SHOW_FPS); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index 4a6c1bb811..f4320005a9 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -107,6 +107,8 @@ struct VideoConfig final AspectMode aspect_mode{}; AspectMode suggested_aspect_mode{}; u32 widescreen_heuristic_transition_threshold = 0; + float widescreen_heuristic_aspect_ratio_ideal = 0.f; + float widescreen_heuristic_aspect_ratio_slop = 0.f; bool bCrop = false; // Aspect ratio controls. bool bShaderCache = false; From 589834f56229edc217d9f2bc1805961cabde9028 Mon Sep 17 00:00:00 2001 From: iwubcode Date: Tue, 5 Sep 2023 21:11:19 -0500 Subject: [PATCH 084/120] VideoCommon: add cubemap as a sampler target for shaders, add cubemap as a valid texture asset --- Source/Core/VideoCommon/Assets/ShaderAsset.cpp | 4 ++++ Source/Core/VideoCommon/Assets/ShaderAsset.h | 3 ++- Source/Core/VideoCommon/Assets/TextureAsset.cpp | 14 +++++++++----- Source/Core/VideoCommon/Assets/TextureAsset.h | 3 ++- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Source/Core/VideoCommon/Assets/ShaderAsset.cpp b/Source/Core/VideoCommon/Assets/ShaderAsset.cpp index af5dad4a93..cbb043095c 100644 --- a/Source/Core/VideoCommon/Assets/ShaderAsset.cpp +++ b/Source/Core/VideoCommon/Assets/ShaderAsset.cpp @@ -49,6 +49,10 @@ bool ParseShaderProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset { property.m_type = ShaderProperty::Type::Type_Sampler2D; } + else if (type == "samplercube") + { + property.m_type = ShaderProperty::Type::Type_SamplerCube; + } else if (type == "samplerarrayshared_main") { property.m_type = ShaderProperty::Type::Type_SamplerArrayShared_Main; diff --git a/Source/Core/VideoCommon/Assets/ShaderAsset.h b/Source/Core/VideoCommon/Assets/ShaderAsset.h index 0675adff02..0a66fc8826 100644 --- a/Source/Core/VideoCommon/Assets/ShaderAsset.h +++ b/Source/Core/VideoCommon/Assets/ShaderAsset.h @@ -26,7 +26,8 @@ struct ShaderProperty Type_SamplerArrayShared_Main, Type_SamplerArrayShared_Additional, Type_Sampler2D, - Type_Max = Type_Sampler2D + Type_SamplerCube, + Type_Max = Type_SamplerCube }; Type m_type; std::string m_description; diff --git a/Source/Core/VideoCommon/Assets/TextureAsset.cpp b/Source/Core/VideoCommon/Assets/TextureAsset.cpp index 8df36397e8..8cd92aa89f 100644 --- a/Source/Core/VideoCommon/Assets/TextureAsset.cpp +++ b/Source/Core/VideoCommon/Assets/TextureAsset.cpp @@ -136,6 +136,15 @@ bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id, if (type == "texture2d") { data->m_type = TextureData::Type::Type_Texture2D; + + if (!ParseSampler(asset_id, json, &data->m_sampler)) + { + return false; + } + } + else if (type == "texturecube") + { + data->m_type = TextureData::Type::Type_TextureCube; } else { @@ -146,11 +155,6 @@ bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id, return false; } - if (!ParseSampler(asset_id, json, &data->m_sampler)) - { - return false; - } - return true; } diff --git a/Source/Core/VideoCommon/Assets/TextureAsset.h b/Source/Core/VideoCommon/Assets/TextureAsset.h index 03b6a231d4..ef4341c83d 100644 --- a/Source/Core/VideoCommon/Assets/TextureAsset.h +++ b/Source/Core/VideoCommon/Assets/TextureAsset.h @@ -28,7 +28,8 @@ struct TextureData { Type_Undefined, Type_Texture2D, - Type_Max = Type_Texture2D + Type_TextureCube, + Type_Max = Type_TextureCube }; Type m_type; CustomTextureData m_data; From 38bd04c439e33ff8411f4590cd6e287080e4d31e Mon Sep 17 00:00:00 2001 From: LillyJadeKatrin Date: Sat, 17 Jun 2023 12:40:07 -0400 Subject: [PATCH 085/120] Add Rich Presence to Achievement Dialog Header This refactors the Rich Presence generation to store to a member field that can be exposed to the UI to display the Rich Presence in the achievement header. It still updates at its original rate of once per two minutes, but calls an update on the dialog when that ticks. --- Source/Core/Core/AchievementManager.cpp | 26 ++++++++++++------- Source/Core/Core/AchievementManager.h | 4 ++- .../Achievements/AchievementHeaderWidget.cpp | 9 +++---- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/Source/Core/Core/AchievementManager.cpp b/Source/Core/Core/AchievementManager.cpp index 74bd257311..2463952e07 100644 --- a/Source/Core/Core/AchievementManager.cpp +++ b/Source/Core/Core/AchievementManager.cpp @@ -290,9 +290,11 @@ void AchievementManager::DoFrame() time_t current_time = std::time(nullptr); if (difftime(current_time, m_last_ping_time) > 120) { - RichPresence rp = GenerateRichPresence(); - m_queue.EmplaceItem([this, rp] { PingRichPresence(rp); }); + GenerateRichPresence(); + m_queue.EmplaceItem([this] { PingRichPresence(m_rich_presence); }); m_last_ping_time = current_time; + if (m_update_callback) + m_update_callback(); } } @@ -305,17 +307,17 @@ u32 AchievementManager::MemoryPeeker(u32 address, u32 num_bytes, void* ud) { case 1: return m_system->GetMMU() - .HostTryReadU8(threadguard, address) + .HostTryReadU8(threadguard, address, PowerPC::RequestedAddressSpace::Physical) .value_or(PowerPC::ReadResult(false, 0u)) .value; case 2: return m_system->GetMMU() - .HostTryReadU16(threadguard, address) + .HostTryReadU16(threadguard, address, PowerPC::RequestedAddressSpace::Physical) .value_or(PowerPC::ReadResult(false, 0u)) .value; case 4: return m_system->GetMMU() - .HostTryReadU32(threadguard, address) + .HostTryReadU32(threadguard, address, PowerPC::RequestedAddressSpace::Physical) .value_or(PowerPC::ReadResult(false, 0u)) .value; default: @@ -411,6 +413,13 @@ void AchievementManager::GetAchievementProgress(AchievementId achievement_id, u3 rc_runtime_get_achievement_measured(&m_runtime, achievement_id, value, target); } +AchievementManager::RichPresence AchievementManager::GetRichPresence() +{ + std::lock_guard lg{m_lock}; + RichPresence rich_presence = m_rich_presence; + return rich_presence; +} + void AchievementManager::CloseGame() { { @@ -585,18 +594,17 @@ void AchievementManager::ActivateDeactivateAchievement(AchievementId id, bool en rc_runtime_deactivate_achievement(&m_runtime, id); } -AchievementManager::RichPresence AchievementManager::GenerateRichPresence() +void AchievementManager::GenerateRichPresence() { - RichPresence rp_buffer; Core::RunAsCPUThread([&] { + std::lock_guard lg{m_lock}; rc_runtime_get_richpresence( - &m_runtime, rp_buffer.data(), RP_SIZE, + &m_runtime, m_rich_presence.data(), RP_SIZE, [](unsigned address, unsigned num_bytes, void* ud) { return static_cast(ud)->MemoryPeeker(address, num_bytes, ud); }, this, nullptr); }); - return rp_buffer; } AchievementManager::ResponseType AchievementManager::AwardAchievement(AchievementId achievement_id) diff --git a/Source/Core/Core/AchievementManager.h b/Source/Core/Core/AchievementManager.h index a2e159c7e3..a4e333d6dc 100644 --- a/Source/Core/Core/AchievementManager.h +++ b/Source/Core/Core/AchievementManager.h @@ -94,6 +94,7 @@ public: rc_api_fetch_game_data_response_t* GetGameData(); UnlockStatus GetUnlockStatus(AchievementId achievement_id) const; void GetAchievementProgress(AchievementId achievement_id, u32* value, u32* target); + RichPresence GetRichPresence(); void CloseGame(); void Logout(); @@ -111,7 +112,7 @@ private: ResponseType FetchUnlockData(bool hardcore); void ActivateDeactivateAchievement(AchievementId id, bool enabled, bool unofficial, bool encore); - RichPresence GenerateRichPresence(); + void GenerateRichPresence(); ResponseType AwardAchievement(AchievementId achievement_id); ResponseType SubmitLeaderboard(AchievementId leaderboard_id, int value); @@ -137,6 +138,7 @@ private: u32 m_game_id = 0; rc_api_fetch_game_data_response_t m_game_data{}; bool m_is_game_loaded = false; + RichPresence m_rich_presence; time_t m_last_ping_time = 0; std::unordered_map m_unlock_map; diff --git a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp index 4dc3e66538..2d817a6891 100644 --- a/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp +++ b/Source/Core/DolphinQt/Achievements/AchievementHeaderWidget.cpp @@ -20,6 +20,7 @@ #include #include "Core/AchievementManager.h" +#include "Core/Config/AchievementSettings.h" #include "Core/Core.h" #include "DolphinQt/QtUtils/ModalMessageBox.h" @@ -101,11 +102,9 @@ void AchievementHeaderWidget::UpdateData() m_game_progress_soft->setValue(point_spread.hard_unlocks); m_game_progress_soft->setRange(0, point_spread.total_count); m_game_progress_soft->setValue(point_spread.hard_unlocks + point_spread.soft_unlocks); - // TODO: RP needs a minor refactor to work here, will be a future PR - // m_rich_presence->setText(QString::fromStdString(AchievementManager::GetInstance()->GenerateRichPresence())); - // m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED)); - m_rich_presence->setText(QString{}); - m_rich_presence->setVisible(false); + m_rich_presence->setText( + QString::fromUtf8(AchievementManager::GetInstance()->GetRichPresence().data())); + m_rich_presence->setVisible(Config::Get(Config::RA_RICH_PRESENCE_ENABLED)); m_user_box->setVisible(false); m_game_box->setVisible(true); From 7cc4304918609ff3a09e996ad03c1742dda5ca51 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Wed, 6 Sep 2023 18:44:43 -0500 Subject: [PATCH 086/120] VideoCommon: Expose the widescreen heuristic's standard and widescreen ratio values in onion config. --- Source/Core/Core/Config/GraphicsSettings.cpp | 6 ++- Source/Core/Core/Config/GraphicsSettings.h | 3 +- Source/Core/VideoCommon/VertexManagerBase.cpp | 38 +++++++++++-------- Source/Core/VideoCommon/VideoConfig.cpp | 6 ++- Source/Core/VideoCommon/VideoConfig.h | 3 +- 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp index 0aaab5ded2..ea8aa05f48 100644 --- a/Source/Core/Core/Config/GraphicsSettings.cpp +++ b/Source/Core/Core/Config/GraphicsSettings.cpp @@ -25,10 +25,12 @@ const Info GFX_SUGGESTED_ASPECT_RATIO{{System::GFX, "Settings", "Sug AspectMode::Auto}; const Info GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD{ {System::GFX, "Settings", "WidescreenHeuristicTransitionThreshold"}, 3}; -const Info GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_IDEAL{ - {System::GFX, "Settings", "WidescreenHeuristicAspectRatioIdeal"}, (16 / 9.f) / (4 / 3.f)}; const Info GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_SLOP{ {System::GFX, "Settings", "WidescreenHeuristicAspectRatioSlop"}, 0.11f}; +const Info GFX_WIDESCREEN_HEURISTIC_STANDARD_RATIO{ + {System::GFX, "Settings", "WidescreenHeuristicStandardRatio"}, 1.f}; +const Info GFX_WIDESCREEN_HEURISTIC_WIDESCREEN_RATIO{ + {System::GFX, "Settings", "WidescreenHeuristicWidescreenRatio"}, (16 / 9.f) / (4 / 3.f)}; const Info GFX_CROP{{System::GFX, "Settings", "Crop"}, false}; const Info GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES{ {System::GFX, "Settings", "SafeTextureCacheColorSamples"}, 128}; diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h index 26e741dfde..33f08daf27 100644 --- a/Source/Core/Core/Config/GraphicsSettings.h +++ b/Source/Core/Core/Config/GraphicsSettings.h @@ -30,8 +30,9 @@ extern const Info GFX_WIDESCREEN_HACK; extern const Info GFX_ASPECT_RATIO; extern const Info GFX_SUGGESTED_ASPECT_RATIO; extern const Info GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD; -extern const Info GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_IDEAL; extern const Info GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_SLOP; +extern const Info GFX_WIDESCREEN_HEURISTIC_STANDARD_RATIO; +extern const Info GFX_WIDESCREEN_HEURISTIC_WIDESCREEN_RATIO; extern const Info GFX_CROP; extern const Info GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES; extern const Info GFX_SHOW_FPS; diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index e1d294f331..2aac0f6f90 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -77,25 +77,33 @@ constexpr Common::EnumMap primitive_fr // Just in case any game decides to take this into account, we do both these // tests with a large amount of slop. -static bool IsAnamorphicProjection(const Projection::Raw& projection, const Viewport& viewport, - const float ideal_ratio, const float slop) +static float CalculateProjectionViewportRatio(const Projection::Raw& projection, + const Viewport& viewport) { - // If ratio between our projection and viewport aspect ratios is similar to 16:9 / 4:3 - // (the "ideal ratio"), we have an anamorphic projection. This value can be overridden - // by a GameINI. - const float projection_ar = projection[2] / projection[0]; const float viewport_ar = viewport.wd / viewport.ht; - return std::abs(std::abs(projection_ar / viewport_ar) - ideal_ratio) < ideal_ratio * slop; + return std::abs(projection_ar / viewport_ar); +} + +static bool IsAnamorphicProjection(const Projection::Raw& projection, const Viewport& viewport, + const VideoConfig& config) +{ + // If ratio between our projection and viewport aspect ratios is similar to 16:9 / 4:3 + // we have an anamorphic projection. This value can be overridden + // by a GameINI. + + return std::abs(CalculateProjectionViewportRatio(projection, viewport) - + config.widescreen_heuristic_widescreen_ratio) < + config.widescreen_heuristic_aspect_ratio_slop; } static bool IsNormalProjection(const Projection::Raw& projection, const Viewport& viewport, - const float slop) + const VideoConfig& config) { - const float projection_ar = projection[2] / projection[0]; - const float viewport_ar = viewport.wd / viewport.ht; - return std::abs(std::abs(projection_ar / viewport_ar) - 1) < slop; + return std::abs(CalculateProjectionViewportRatio(projection, viewport) - + config.widescreen_heuristic_standard_ratio) < + config.widescreen_heuristic_aspect_ratio_slop; } VertexManagerBase::VertexManagerBase() @@ -507,15 +515,15 @@ void VertexManagerBase::Flush() auto& counts = is_perspective ? m_flush_statistics.perspective : m_flush_statistics.orthographic; - const float ideal_ratio = g_ActiveConfig.widescreen_heuristic_aspect_ratio_ideal; - const float slop = g_ActiveConfig.widescreen_heuristic_aspect_ratio_slop; + // TODO: Potentially the viewport size could be used as weight for the flush count average. + // This way a small minimap would have less effect than a fullscreen projection. - if (IsAnamorphicProjection(xfmem.projection.rawProjection, xfmem.viewport, ideal_ratio, slop)) + if (IsAnamorphicProjection(xfmem.projection.rawProjection, xfmem.viewport, g_ActiveConfig)) { ++counts.anamorphic_flush_count; counts.anamorphic_vertex_count += m_index_generator.GetIndexLen(); } - else if (IsNormalProjection(xfmem.projection.rawProjection, xfmem.viewport, slop)) + else if (IsNormalProjection(xfmem.projection.rawProjection, xfmem.viewport, g_ActiveConfig)) { ++counts.normal_flush_count; counts.normal_vertex_count += m_index_generator.GetIndexLen(); diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index e2096dfd1f..69f294f827 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -88,10 +88,12 @@ void VideoConfig::Refresh() suggested_aspect_mode = Config::Get(Config::GFX_SUGGESTED_ASPECT_RATIO); widescreen_heuristic_transition_threshold = Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD); - widescreen_heuristic_aspect_ratio_ideal = - Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_IDEAL); widescreen_heuristic_aspect_ratio_slop = Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_ASPECT_RATIO_SLOP); + widescreen_heuristic_standard_ratio = + Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_STANDARD_RATIO); + widescreen_heuristic_widescreen_ratio = + Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_WIDESCREEN_RATIO); bCrop = Config::Get(Config::GFX_CROP); iSafeTextureCache_ColorSamples = Config::Get(Config::GFX_SAFE_TEXTURE_CACHE_COLOR_SAMPLES); bShowFPS = Config::Get(Config::GFX_SHOW_FPS); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index f4320005a9..5e315fbcca 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -107,8 +107,9 @@ struct VideoConfig final AspectMode aspect_mode{}; AspectMode suggested_aspect_mode{}; u32 widescreen_heuristic_transition_threshold = 0; - float widescreen_heuristic_aspect_ratio_ideal = 0.f; float widescreen_heuristic_aspect_ratio_slop = 0.f; + float widescreen_heuristic_standard_ratio = 0.f; + float widescreen_heuristic_widescreen_ratio = 0.f; bool bCrop = false; // Aspect ratio controls. bool bShaderCache = false; From a20bb3e05bac6e3fbeee4920c7d64a9cd3760d9f Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Thu, 7 Sep 2023 04:00:22 +0200 Subject: [PATCH 087/120] Common/MemArena: Assert return value of LazyMemoryRegion::Clear()'s mmap() call. --- Source/Core/Common/MemArenaAndroid.cpp | 4 +++- Source/Core/Common/MemArenaUnix.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/Core/Common/MemArenaAndroid.cpp b/Source/Core/Common/MemArenaAndroid.cpp index 4a9e2f68b4..45b685274b 100644 --- a/Source/Core/Common/MemArenaAndroid.cpp +++ b/Source/Core/Common/MemArenaAndroid.cpp @@ -172,7 +172,9 @@ void LazyMemoryRegion::Clear() { ASSERT(m_memory); - mmap(m_memory, m_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + void* new_memory = mmap(m_memory, m_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + ASSERT(new_memory == m_memory); } void LazyMemoryRegion::Release() diff --git a/Source/Core/Common/MemArenaUnix.cpp b/Source/Core/Common/MemArenaUnix.cpp index 452c2c50c8..f962da755a 100644 --- a/Source/Core/Common/MemArenaUnix.cpp +++ b/Source/Core/Common/MemArenaUnix.cpp @@ -138,7 +138,9 @@ void LazyMemoryRegion::Clear() { ASSERT(m_memory); - mmap(m_memory, m_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + void* new_memory = mmap(m_memory, m_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + ASSERT(new_memory == m_memory); } void LazyMemoryRegion::Release() From c14bc6ea4c85d823d39d6b49972e0acc493652b0 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Thu, 7 Sep 2023 13:59:33 +0200 Subject: [PATCH 088/120] Common/MemArena: mmap returns MAP_FAILED on error, not nullptr. --- Source/Core/Common/MemArenaAndroid.cpp | 2 +- Source/Core/Common/MemArenaUnix.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/Common/MemArenaAndroid.cpp b/Source/Core/Common/MemArenaAndroid.cpp index 45b685274b..3c4b800da8 100644 --- a/Source/Core/Common/MemArenaAndroid.cpp +++ b/Source/Core/Common/MemArenaAndroid.cpp @@ -156,7 +156,7 @@ void* LazyMemoryRegion::Create(size_t size) ASSERT(!m_memory); void* memory = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (!memory) + if (memory == MAP_FAILED) { NOTICE_LOG_FMT(MEMMAP, "Memory allocation of {} bytes failed.", size); return nullptr; diff --git a/Source/Core/Common/MemArenaUnix.cpp b/Source/Core/Common/MemArenaUnix.cpp index f962da755a..ef359de908 100644 --- a/Source/Core/Common/MemArenaUnix.cpp +++ b/Source/Core/Common/MemArenaUnix.cpp @@ -122,7 +122,7 @@ void* LazyMemoryRegion::Create(size_t size) ASSERT(!m_memory); void* memory = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (!memory) + if (memory == MAP_FAILED) { NOTICE_LOG_FMT(MEMMAP, "Memory allocation of {} bytes failed.", size); return nullptr; From 422bc7a62724643a4d0e92adf857883f73aa9ca6 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Thu, 7 Sep 2023 14:41:24 +0200 Subject: [PATCH 089/120] Common/MemArena: A zero-byte allocation is invalid. --- Source/Core/Common/MemArenaAndroid.cpp | 3 +++ Source/Core/Common/MemArenaUnix.cpp | 3 +++ Source/Core/Common/MemArenaWin.cpp | 3 +++ 3 files changed, 9 insertions(+) diff --git a/Source/Core/Common/MemArenaAndroid.cpp b/Source/Core/Common/MemArenaAndroid.cpp index 3c4b800da8..bcba64e1e1 100644 --- a/Source/Core/Common/MemArenaAndroid.cpp +++ b/Source/Core/Common/MemArenaAndroid.cpp @@ -155,6 +155,9 @@ void* LazyMemoryRegion::Create(size_t size) { ASSERT(!m_memory); + if (size == 0) + return nullptr; + void* memory = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (memory == MAP_FAILED) { diff --git a/Source/Core/Common/MemArenaUnix.cpp b/Source/Core/Common/MemArenaUnix.cpp index ef359de908..9bf3633ee2 100644 --- a/Source/Core/Common/MemArenaUnix.cpp +++ b/Source/Core/Common/MemArenaUnix.cpp @@ -121,6 +121,9 @@ void* LazyMemoryRegion::Create(size_t size) { ASSERT(!m_memory); + if (size == 0) + return nullptr; + void* memory = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (memory == MAP_FAILED) { diff --git a/Source/Core/Common/MemArenaWin.cpp b/Source/Core/Common/MemArenaWin.cpp index ebf078f45b..b147ced53f 100644 --- a/Source/Core/Common/MemArenaWin.cpp +++ b/Source/Core/Common/MemArenaWin.cpp @@ -445,6 +445,9 @@ void* LazyMemoryRegion::Create(size_t size) { ASSERT(!m_memory); + if (size == 0) + return nullptr; + void* memory = VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if (!memory) { From cf2a1f29b7efbd97f8d0197838c147a56b270502 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Thu, 7 Sep 2023 14:47:52 +0200 Subject: [PATCH 090/120] Core/JitCache: Don't try to allocate the fast block map on 32-bit builds. --- Source/Core/Core/PowerPC/JitCommon/JitCache.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp index 98ce48f24a..95fad45bde 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp @@ -42,7 +42,11 @@ void JitBaseBlockCache::Init() { Common::JitRegister::Init(Config::Get(Config::MAIN_PERF_MAP_DIR)); +#ifdef _ARCH_64 m_fast_block_map = reinterpret_cast(m_block_map_arena.Create(FAST_BLOCK_MAP_SIZE)); +#else + m_fast_block_map = nullptr; +#endif if (m_fast_block_map) m_fast_block_map_ptr = m_fast_block_map; else From 860acfb15dbdf7d3589c0d687af543dc7d70b2fc Mon Sep 17 00:00:00 2001 From: R Date: Thu, 7 Sep 2023 00:37:01 +0100 Subject: [PATCH 091/120] Steam Deck: Periodically reenable gyro --- .../SteamDeck/SteamDeck.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Source/Core/InputCommon/ControllerInterface/SteamDeck/SteamDeck.cpp b/Source/Core/InputCommon/ControllerInterface/SteamDeck/SteamDeck.cpp index cfdf3e9649..0a0dc88387 100644 --- a/Source/Core/InputCommon/ControllerInterface/SteamDeck/SteamDeck.cpp +++ b/Source/Core/InputCommon/ControllerInterface/SteamDeck/SteamDeck.cpp @@ -118,6 +118,7 @@ public: private: hid_device* m_device; DeckInputReport m_latest_input; + int m_gyro_reenable = 0; }; class InputBackend final : public ciface::InputBackend @@ -280,6 +281,24 @@ std::string Device::GetSource() const void Device::UpdateInput() { + // As of a certain mid-2023 update to the Steam client, + // Steam will disable gyro data if gyro is not mapped in Steam Input. + // This disabling will happen every time the Steam overlay is closed. + // This command turns gyro data back on periodically. + if (++m_gyro_reenable == 250) + { + m_gyro_reenable = 0; + // Using names from Valve's contribution to SDL for the Steam Controller + // (and assuming this has not changed for the Deck), this packet decodes as: + // 0x00 = report ID + // 0x87 = ID_SET_SETTINGS_VALUES + // 0x03 = payload length + // 0x30 = SETTING_GYRO_MODE + // 0x18 0x00 = SETTING_GYRO_MODE_SEND_RAW_ACCEL | SETTING_GYRO_MODE_SEND_RAW_GYRO + const unsigned char pkt[] = {0x00, 0x87, 0x03, 0x30, 0x18, 0x00}; + hid_send_feature_report(m_device, pkt, sizeof(pkt)); + } + DeckInputReport rpt; bool got_anything = false; // Read all available input reports (processing only the most recent one). From 911c469cf553ca6f754c7dc6c665e2c27924388f Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 4 Sep 2023 05:14:13 +0200 Subject: [PATCH 092/120] Common/LogManager: Add logging category for Achievements. --- Source/Core/Common/Logging/Log.h | 1 + Source/Core/Common/Logging/LogManager.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/Source/Core/Common/Logging/Log.h b/Source/Core/Common/Logging/Log.h index 29a65a972d..a13e9fee94 100644 --- a/Source/Core/Common/Logging/Log.h +++ b/Source/Core/Common/Logging/Log.h @@ -12,6 +12,7 @@ namespace Common::Log { enum class LogType : int { + ACHIEVEMENTS, ACTIONREPLAY, AUDIO, AUDIO_INTERFACE, diff --git a/Source/Core/Common/Logging/LogManager.cpp b/Source/Core/Common/Logging/LogManager.cpp index 80e7db895e..749117bc74 100644 --- a/Source/Core/Common/Logging/LogManager.cpp +++ b/Source/Core/Common/Logging/LogManager.cpp @@ -96,6 +96,7 @@ static size_t DeterminePathCutOffPoint() LogManager::LogManager() { // create log containers + m_log[LogType::ACHIEVEMENTS] = {"RetroAchievements", "Achievements"}; m_log[LogType::ACTIONREPLAY] = {"ActionReplay", "Action Replay"}; m_log[LogType::AUDIO] = {"Audio", "Audio Emulator"}; m_log[LogType::AUDIO_INTERFACE] = {"AI", "Audio Interface"}; From f7f4da2be89b3973c73b482ba8ab46aa19b2c772 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Fri, 25 Aug 2023 17:05:34 +0200 Subject: [PATCH 093/120] Jit: Use correct address when checking fifoWriteAddresses We need to check for the address of the *previous* instruction, because checking fifoWriteAddresses happens not at the end of the instruction that triggered it but at the start of the next instruction. --- Source/Core/Core/PowerPC/Jit64/Jit.cpp | 91 +++++++------ Source/Core/Core/PowerPC/JitArm64/Jit.cpp | 156 +++++++++++----------- 2 files changed, 129 insertions(+), 118 deletions(-) diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.cpp b/Source/Core/Core/PowerPC/Jit64/Jit.cpp index e5aa885263..9fe8e1430c 100644 --- a/Source/Core/Core/PowerPC/Jit64/Jit.cpp +++ b/Source/Core/Core/PowerPC/Jit64/Jit.cpp @@ -949,53 +949,58 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC) js.isLastInstruction = true; } - // Gather pipe writes using a non-immediate address are discovered by profiling. - bool gatherPipeIntCheck = js.fifoWriteAddresses.find(op.address) != js.fifoWriteAddresses.end(); - - // Gather pipe writes using an immediate address are explicitly tracked. - if (jo.optimizeGatherPipe && - (js.fifoBytesSinceCheck >= GPFifo::GATHER_PIPE_SIZE || js.mustCheckFifo)) + if (i != 0) { - js.fifoBytesSinceCheck = 0; - js.mustCheckFifo = false; - BitSet32 registersInUse = CallerSavedRegistersInUse(); - ABI_PushRegistersAndAdjustStack(registersInUse, 0); - ABI_CallFunctionP(GPFifo::FastCheckGatherPipe, &m_system.GetGPFifo()); - ABI_PopRegistersAndAdjustStack(registersInUse, 0); - gatherPipeIntCheck = true; - } - - // Gather pipe writes can generate an exception; add an exception check. - // TODO: This doesn't really match hardware; the CP interrupt is - // asynchronous. - if (gatherPipeIntCheck) - { - TEST(32, PPCSTATE(Exceptions), Imm32(EXCEPTION_EXTERNAL_INT)); - FixupBranch extException = J_CC(CC_NZ, Jump::Near); - - SwitchToFarCode(); - SetJumpTarget(extException); - TEST(32, PPCSTATE(msr), Imm32(0x0008000)); - FixupBranch noExtIntEnable = J_CC(CC_Z, Jump::Near); - MOV(64, R(RSCRATCH), ImmPtr(&m_system.GetProcessorInterface().m_interrupt_cause)); - TEST(32, MatR(RSCRATCH), - Imm32(ProcessorInterface::INT_CAUSE_CP | ProcessorInterface::INT_CAUSE_PE_TOKEN | - ProcessorInterface::INT_CAUSE_PE_FINISH)); - FixupBranch noCPInt = J_CC(CC_Z, Jump::Near); + // Gather pipe writes using a non-immediate address are discovered by profiling. + const u32 prev_address = m_code_buffer[i - 1].address; + bool gatherPipeIntCheck = + js.fifoWriteAddresses.find(prev_address) != js.fifoWriteAddresses.end(); + // Gather pipe writes using an immediate address are explicitly tracked. + if (jo.optimizeGatherPipe && + (js.fifoBytesSinceCheck >= GPFifo::GATHER_PIPE_SIZE || js.mustCheckFifo)) { - RCForkGuard gpr_guard = gpr.Fork(); - RCForkGuard fpr_guard = fpr.Fork(); - - gpr.Flush(); - fpr.Flush(); - - MOV(32, PPCSTATE(pc), Imm32(op.address)); - WriteExternalExceptionExit(); + js.fifoBytesSinceCheck = 0; + js.mustCheckFifo = false; + BitSet32 registersInUse = CallerSavedRegistersInUse(); + ABI_PushRegistersAndAdjustStack(registersInUse, 0); + ABI_CallFunctionP(GPFifo::FastCheckGatherPipe, &m_system.GetGPFifo()); + ABI_PopRegistersAndAdjustStack(registersInUse, 0); + gatherPipeIntCheck = true; + } + + // Gather pipe writes can generate an exception; add an exception check. + // TODO: This doesn't really match hardware; the CP interrupt is + // asynchronous. + if (gatherPipeIntCheck) + { + TEST(32, PPCSTATE(Exceptions), Imm32(EXCEPTION_EXTERNAL_INT)); + FixupBranch extException = J_CC(CC_NZ, Jump::Near); + + SwitchToFarCode(); + SetJumpTarget(extException); + TEST(32, PPCSTATE(msr), Imm32(0x0008000)); + FixupBranch noExtIntEnable = J_CC(CC_Z, Jump::Near); + MOV(64, R(RSCRATCH), ImmPtr(&m_system.GetProcessorInterface().m_interrupt_cause)); + TEST(32, MatR(RSCRATCH), + Imm32(ProcessorInterface::INT_CAUSE_CP | ProcessorInterface::INT_CAUSE_PE_TOKEN | + ProcessorInterface::INT_CAUSE_PE_FINISH)); + FixupBranch noCPInt = J_CC(CC_Z, Jump::Near); + + { + RCForkGuard gpr_guard = gpr.Fork(); + RCForkGuard fpr_guard = fpr.Fork(); + + gpr.Flush(); + fpr.Flush(); + + MOV(32, PPCSTATE(pc), Imm32(op.address)); + WriteExternalExceptionExit(); + } + SwitchToNearCode(); + SetJumpTarget(noCPInt); + SetJumpTarget(noExtIntEnable); } - SwitchToNearCode(); - SetJumpTarget(noCPInt); - SetJumpTarget(noExtIntEnable); } if (HandleFunctionHooking(op.address)) diff --git a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp index 236a0d939f..55c6b75231 100644 --- a/Source/Core/Core/PowerPC/JitArm64/Jit.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/Jit.cpp @@ -1047,90 +1047,96 @@ bool JitArm64::DoJit(u32 em_address, JitBlock* b, u32 nextPC) fpr_used[op.fregOut] = true; fpr.UpdateLastUsed(fpr_used); - // Gather pipe writes using a non-immediate address are discovered by profiling. - bool gatherPipeIntCheck = js.fifoWriteAddresses.find(op.address) != js.fifoWriteAddresses.end(); - - if (jo.optimizeGatherPipe && - (js.fifoBytesSinceCheck >= GPFifo::GATHER_PIPE_SIZE || js.mustCheckFifo)) + if (i != 0) { - js.fifoBytesSinceCheck = 0; - js.mustCheckFifo = false; + // Gather pipe writes using a non-immediate address are discovered by profiling. + const u32 prev_address = m_code_buffer[i - 1].address; + bool gatherPipeIntCheck = + js.fifoWriteAddresses.find(prev_address) != js.fifoWriteAddresses.end(); - gpr.Lock(ARM64Reg::W30); - BitSet32 regs_in_use = gpr.GetCallerSavedUsed(); - BitSet32 fprs_in_use = fpr.GetCallerSavedUsed(); - regs_in_use[DecodeReg(ARM64Reg::W30)] = 0; + if (jo.optimizeGatherPipe && + (js.fifoBytesSinceCheck >= GPFifo::GATHER_PIPE_SIZE || js.mustCheckFifo)) + { + js.fifoBytesSinceCheck = 0; + js.mustCheckFifo = false; - ABI_PushRegisters(regs_in_use); - m_float_emit.ABI_PushRegisters(fprs_in_use, ARM64Reg::X30); - MOVP2R(ARM64Reg::X8, &GPFifo::FastCheckGatherPipe); - MOVP2R(ARM64Reg::X0, &m_system.GetGPFifo()); - BLR(ARM64Reg::X8); - m_float_emit.ABI_PopRegisters(fprs_in_use, ARM64Reg::X30); - ABI_PopRegisters(regs_in_use); + gpr.Lock(ARM64Reg::W30); + BitSet32 regs_in_use = gpr.GetCallerSavedUsed(); + BitSet32 fprs_in_use = fpr.GetCallerSavedUsed(); + regs_in_use[DecodeReg(ARM64Reg::W30)] = 0; - // Inline exception check - LDR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(Exceptions)); - FixupBranch no_ext_exception = TBZ(ARM64Reg::W30, MathUtil::IntLog2(EXCEPTION_EXTERNAL_INT)); - FixupBranch exception = B(); - SwitchToFarCode(); - const u8* done_here = GetCodePtr(); - FixupBranch exit = B(); - SetJumpTarget(exception); - LDR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(msr)); - TBZ(ARM64Reg::W30, 15, done_here); // MSR.EE - LDR(IndexType::Unsigned, ARM64Reg::W30, ARM64Reg::X30, - MOVPage2R(ARM64Reg::X30, &m_system.GetProcessorInterface().m_interrupt_cause)); - constexpr u32 cause_mask = ProcessorInterface::INT_CAUSE_CP | - ProcessorInterface::INT_CAUSE_PE_TOKEN | - ProcessorInterface::INT_CAUSE_PE_FINISH; - TST(ARM64Reg::W30, LogicalImm(cause_mask, 32)); - B(CC_EQ, done_here); + ABI_PushRegisters(regs_in_use); + m_float_emit.ABI_PushRegisters(fprs_in_use, ARM64Reg::X30); + MOVP2R(ARM64Reg::X8, &GPFifo::FastCheckGatherPipe); + MOVP2R(ARM64Reg::X0, &m_system.GetGPFifo()); + BLR(ARM64Reg::X8); + m_float_emit.ABI_PopRegisters(fprs_in_use, ARM64Reg::X30); + ABI_PopRegisters(regs_in_use); - gpr.Flush(FlushMode::MaintainState, ARM64Reg::W30); - fpr.Flush(FlushMode::MaintainState, ARM64Reg::INVALID_REG); - WriteExceptionExit(js.compilerPC, true, true); - SwitchToNearCode(); - SetJumpTarget(no_ext_exception); - SetJumpTarget(exit); - gpr.Unlock(ARM64Reg::W30); + // Inline exception check + LDR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(Exceptions)); + FixupBranch no_ext_exception = + TBZ(ARM64Reg::W30, MathUtil::IntLog2(EXCEPTION_EXTERNAL_INT)); + FixupBranch exception = B(); + SwitchToFarCode(); + const u8* done_here = GetCodePtr(); + FixupBranch exit = B(); + SetJumpTarget(exception); + LDR(IndexType::Unsigned, ARM64Reg::W30, PPC_REG, PPCSTATE_OFF(msr)); + TBZ(ARM64Reg::W30, 15, done_here); // MSR.EE + LDR(IndexType::Unsigned, ARM64Reg::W30, ARM64Reg::X30, + MOVPage2R(ARM64Reg::X30, &m_system.GetProcessorInterface().m_interrupt_cause)); + constexpr u32 cause_mask = ProcessorInterface::INT_CAUSE_CP | + ProcessorInterface::INT_CAUSE_PE_TOKEN | + ProcessorInterface::INT_CAUSE_PE_FINISH; + TST(ARM64Reg::W30, LogicalImm(cause_mask, 32)); + B(CC_EQ, done_here); - // So we don't check exceptions twice - gatherPipeIntCheck = false; - } - // Gather pipe writes can generate an exception; add an exception check. - // TODO: This doesn't really match hardware; the CP interrupt is - // asynchronous. - if (jo.optimizeGatherPipe && gatherPipeIntCheck) - { - ARM64Reg WA = gpr.GetReg(); - ARM64Reg XA = EncodeRegTo64(WA); + gpr.Flush(FlushMode::MaintainState, ARM64Reg::W30); + fpr.Flush(FlushMode::MaintainState, ARM64Reg::INVALID_REG); + WriteExceptionExit(js.compilerPC, true, true); + SwitchToNearCode(); + SetJumpTarget(no_ext_exception); + SetJumpTarget(exit); + gpr.Unlock(ARM64Reg::W30); - LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(Exceptions)); - FixupBranch no_ext_exception = TBZ(WA, MathUtil::IntLog2(EXCEPTION_EXTERNAL_INT)); - FixupBranch exception = B(); - SwitchToFarCode(); - const u8* done_here = GetCodePtr(); - FixupBranch exit = B(); - SetJumpTarget(exception); - LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(msr)); - TBZ(WA, 15, done_here); // MSR.EE - LDR(IndexType::Unsigned, WA, XA, - MOVPage2R(XA, &m_system.GetProcessorInterface().m_interrupt_cause)); - constexpr u32 cause_mask = ProcessorInterface::INT_CAUSE_CP | - ProcessorInterface::INT_CAUSE_PE_TOKEN | - ProcessorInterface::INT_CAUSE_PE_FINISH; - TST(WA, LogicalImm(cause_mask, 32)); - B(CC_EQ, done_here); + // So we don't check exceptions twice + gatherPipeIntCheck = false; + } + // Gather pipe writes can generate an exception; add an exception check. + // TODO: This doesn't really match hardware; the CP interrupt is + // asynchronous. + if (jo.optimizeGatherPipe && gatherPipeIntCheck) + { + ARM64Reg WA = gpr.GetReg(); + ARM64Reg XA = EncodeRegTo64(WA); - gpr.Flush(FlushMode::MaintainState, WA); - fpr.Flush(FlushMode::MaintainState, ARM64Reg::INVALID_REG); - WriteExceptionExit(js.compilerPC, true, true); - SwitchToNearCode(); - SetJumpTarget(no_ext_exception); - SetJumpTarget(exit); + LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(Exceptions)); + FixupBranch no_ext_exception = TBZ(WA, MathUtil::IntLog2(EXCEPTION_EXTERNAL_INT)); + FixupBranch exception = B(); + SwitchToFarCode(); + const u8* done_here = GetCodePtr(); + FixupBranch exit = B(); + SetJumpTarget(exception); + LDR(IndexType::Unsigned, WA, PPC_REG, PPCSTATE_OFF(msr)); + TBZ(WA, 15, done_here); // MSR.EE + LDR(IndexType::Unsigned, WA, XA, + MOVPage2R(XA, &m_system.GetProcessorInterface().m_interrupt_cause)); + constexpr u32 cause_mask = ProcessorInterface::INT_CAUSE_CP | + ProcessorInterface::INT_CAUSE_PE_TOKEN | + ProcessorInterface::INT_CAUSE_PE_FINISH; + TST(WA, LogicalImm(cause_mask, 32)); + B(CC_EQ, done_here); - gpr.Unlock(WA); + gpr.Flush(FlushMode::MaintainState, WA); + fpr.Flush(FlushMode::MaintainState, ARM64Reg::INVALID_REG); + WriteExceptionExit(js.compilerPC, true, true); + SwitchToNearCode(); + SetJumpTarget(no_ext_exception); + SetJumpTarget(exit); + + gpr.Unlock(WA); + } } if (HandleFunctionHooking(op.address)) From 5902b5b11351625d347a694f5aadde5d7ade4aed Mon Sep 17 00:00:00 2001 From: JosJuice Date: Fri, 25 Aug 2023 16:15:54 +0200 Subject: [PATCH 094/120] PPCAnalyst: Don't discard before gather pipe interrupt check This bug has been lurking in the code ever since I added the discard functionality. It doesn't seem to be triggered all that often, and on top of that the emitted code only runs conditionally, so I'm not sure if people have been affected by this bug in practice or not. --- Source/Core/Core/PowerPC/PPCAnalyst.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Source/Core/Core/PowerPC/PPCAnalyst.cpp b/Source/Core/Core/PowerPC/PPCAnalyst.cpp index 8e6a1eee00..78a80e80a2 100644 --- a/Source/Core/Core/PowerPC/PPCAnalyst.cpp +++ b/Source/Core/Core/PowerPC/PPCAnalyst.cpp @@ -757,6 +757,16 @@ bool PPCAnalyzer::IsBusyWaitLoop(CodeBlock* block, CodeOp* code, size_t instruct return false; } +static bool CanCauseGatherPipeInterruptCheck(const CodeOp& op) +{ + // eieio + if (op.inst.OPCD == 31 && op.inst.SUBOP10 == 854) + return true; + + return op.opinfo->type == OpType::Store || op.opinfo->type == OpType::StoreFP || + op.opinfo->type == OpType::StorePS; +} + u32 PPCAnalyzer::Analyze(u32 address, CodeBlock* block, CodeBuffer* buffer, std::size_t block_size) const { @@ -952,6 +962,14 @@ u32 PPCAnalyzer::Analyze(u32 address, CodeBlock* block, CodeBuffer* buffer, { CodeOp& op = code[i]; + if (CanCauseGatherPipeInterruptCheck(op)) + { + // If we're doing a gather pipe interrupt check after this instruction, we need to + // be able to flush all registers, so we can't have any discarded registers. + gprDiscardable = BitSet32{}; + fprDiscardable = BitSet32{}; + } + const BitSet8 opWantsCR = op.wantsCR; const bool opWantsFPRF = op.wantsFPRF; const bool opWantsCA = op.wantsCA; From 34b0a6ea90b2a829ced39639d60fbdfbb7af4632 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Fri, 25 Aug 2023 16:06:39 +0200 Subject: [PATCH 095/120] Jit: Check for discarded registers when flushing This adds a check for the bug addressed by the previous commit. --- .../Core/Core/PowerPC/Jit64/RegCache/JitRegCache.cpp | 2 ++ .../Core/Core/PowerPC/JitArm64/JitArm64_RegCache.cpp | 10 ++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.cpp b/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.cpp index 28276f5fa3..9c6c395a57 100644 --- a/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.cpp +++ b/Source/Core/Core/PowerPC/Jit64/RegCache/JitRegCache.cpp @@ -421,7 +421,9 @@ void RegCache::Flush(BitSet32 pregs) switch (m_regs[i].GetLocationType()) { case PPCCachedReg::LocationType::Default: + break; case PPCCachedReg::LocationType::Discarded: + ASSERT_MSG(DYNA_REC, false, "Attempted to flush discarded PPC reg {}", i); break; case PPCCachedReg::LocationType::SpeculativeImmediate: // We can have a cached value without a host register through speculative constants. diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.cpp b/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.cpp index 83bbfe1b3a..a1400a5c9a 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64_RegCache.cpp @@ -245,11 +245,14 @@ void Arm64GPRCache::FlushRegisters(BitSet32 regs, bool maintain_state, ARM64Reg { if (regs[i]) { + ASSERT_MSG(DYNA_REC, m_guest_registers[GUEST_GPR_OFFSET + i].GetType() != RegType::Discarded, + "Attempted to flush discarded register"); + if (i + 1 < GUEST_GPR_COUNT && regs[i + 1]) { // We've got two guest registers in a row to store - OpArg& reg1 = m_guest_registers[i]; - OpArg& reg2 = m_guest_registers[i + 1]; + OpArg& reg1 = m_guest_registers[GUEST_GPR_OFFSET + i]; + OpArg& reg2 = m_guest_registers[GUEST_GPR_OFFSET + i + 1]; if (reg1.IsDirty() && reg2.IsDirty() && reg1.GetType() == RegType::Register && reg2.GetType() == RegType::Register) { @@ -283,6 +286,9 @@ void Arm64GPRCache::FlushCRRegisters(BitSet32 regs, bool maintain_state, ARM64Re { if (regs[i]) { + ASSERT_MSG(DYNA_REC, m_guest_registers[GUEST_CR_OFFSET + i].GetType() != RegType::Discarded, + "Attempted to flush discarded register"); + FlushRegister(GUEST_CR_OFFSET + i, maintain_state, tmp_reg); } } From 3f6a976e0f03ecc631318bb00952e32214e11ee7 Mon Sep 17 00:00:00 2001 From: Sketch <75850871+SketchMaster2001@users.noreply.github.com> Date: Tue, 5 Sep 2023 20:56:53 -0400 Subject: [PATCH 096/120] IOS/KD: Implement Download Scheduler --- Source/Core/Core/IOS/Network/KD/NWC24Config.h | 1 + Source/Core/Core/IOS/Network/KD/NWC24DL.cpp | 72 ++++++++++- Source/Core/Core/IOS/Network/KD/NWC24DL.h | 25 +++- .../Core/Core/IOS/Network/KD/NetKDRequest.cpp | 121 +++++++++++++++++- .../Core/Core/IOS/Network/KD/NetKDRequest.h | 2 + Source/Core/Core/IOS/Network/KD/NetKDTime.h | 8 +- 6 files changed, 215 insertions(+), 14 deletions(-) diff --git a/Source/Core/Core/IOS/Network/KD/NWC24Config.h b/Source/Core/Core/IOS/Network/KD/NWC24Config.h index 2b05d6dfd7..26d37009ff 100644 --- a/Source/Core/Core/IOS/Network/KD/NWC24Config.h +++ b/Source/Core/Core/IOS/Network/KD/NWC24Config.h @@ -32,6 +32,7 @@ enum ErrorCode : s32 WC24_ERR_ID_NONEXISTANCE = -34, WC24_ERR_ID_GENERATED = -35, WC24_ERR_ID_REGISTERED = -36, + WC24_ERR_DISABLED = -39, WC24_ERR_ID_NOT_REGISTERED = -44, }; diff --git a/Source/Core/Core/IOS/Network/KD/NWC24DL.cpp b/Source/Core/Core/IOS/Network/KD/NWC24DL.cpp index e61f3abbf1..34c5e4d1d1 100644 --- a/Source/Core/Core/IOS/Network/KD/NWC24DL.cpp +++ b/Source/Core/Core/IOS/Network/KD/NWC24DL.cpp @@ -59,7 +59,7 @@ void NWC24Dl::WriteDlList() const ERROR_LOG_FMT(IOS_WC24, "Failed to open or write WC24 DL list file"); } -bool NWC24Dl::DoesEntryExist(u16 entry_index) +bool NWC24Dl::DoesEntryExist(u16 entry_index) const { return m_data.entries[entry_index].low_title_id != 0; } @@ -125,6 +125,76 @@ bool NWC24Dl::IsRSASigned(u16 entry_index) const return !Common::ExtractBit(Common::swap32(m_data.entries[entry_index].flags), 2); } +bool NWC24Dl::SkipSchedulerDownload(u16 entry_index) const +{ + // Some entries can be set to not be downloaded by the scheduler. + return !!Common::ExtractBit(Common::swap32(m_data.entries[entry_index].flags), 5); +} + +bool NWC24Dl::HasSubtask(u16 entry_index) const +{ + switch (m_data.entries[entry_index].subtask_type) + { + case 1: + case 2: + case 3: + case 4: + return true; + default: + return false; + } +} + +bool NWC24Dl::IsSubtaskDownloadDisabled(u16 entry_index) const +{ + return !!Common::ExtractBit(Common::swap16(m_data.entries[entry_index].subtask_flags), 9); +} + +bool NWC24Dl::IsValidSubtask(u16 entry_index, u8 subtask_id) const +{ + return !!Common::ExtractBit(m_data.entries[entry_index].subtask_bitmask, subtask_id); +} + +u64 NWC24Dl::GetNextDownloadTime(u16 record_index) const +{ + // Timestamps are stored as a UNIX timestamp but in minutes. We want seconds. + return Common::swap32(m_data.records[record_index].next_dl_timestamp) * SECONDS_PER_MINUTE; +} + +u64 NWC24Dl::GetRetryTime(u16 entry_index) const +{ + const u64 retry_time = Common::swap16(m_data.entries[entry_index].retry_frequency); + if (retry_time == 0) + { + return MINUTES_PER_DAY * SECONDS_PER_MINUTE; + } + return retry_time * SECONDS_PER_MINUTE; +} + +u64 NWC24Dl::GetDownloadMargin(u16 entry_index) const +{ + return Common::swap16(m_data.entries[entry_index].dl_margin) * SECONDS_PER_MINUTE; +} + +void NWC24Dl::SetNextDownloadTime(u16 record_index, u64 value, std::optional subtask_id) +{ + if (subtask_id) + { + m_data.entries[record_index].subtask_timestamps[*subtask_id] = + Common::swap32(static_cast(value / SECONDS_PER_MINUTE)); + } + + m_data.records[record_index].next_dl_timestamp = + Common::swap32(static_cast(value / SECONDS_PER_MINUTE)); +} + +u64 NWC24Dl::GetLastSubtaskDownloadTime(u16 entry_index, u8 subtask_id) const +{ + return Common::swap32(m_data.entries[entry_index].subtask_timestamps[subtask_id]) * + SECONDS_PER_MINUTE + + Common::swap32(m_data.entries[entry_index].server_dl_interval) * SECONDS_PER_MINUTE; +} + u32 NWC24Dl::Magic() const { return Common::swap32(m_data.header.magic); diff --git a/Source/Core/Core/IOS/Network/KD/NWC24DL.h b/Source/Core/Core/IOS/Network/KD/NWC24DL.h index e5427ea2ea..d6cbb8e9c8 100644 --- a/Source/Core/Core/IOS/Network/KD/NWC24DL.h +++ b/Source/Core/Core/IOS/Network/KD/NWC24DL.h @@ -27,14 +27,24 @@ public: s32 CheckNwc24DlList() const; - bool DoesEntryExist(u16 entry_index); + bool DoesEntryExist(u16 entry_index) const; bool IsEncrypted(u16 entry_index) const; bool IsRSASigned(u16 entry_index) const; + bool SkipSchedulerDownload(u16 entry_index) const; + bool HasSubtask(u16 entry_index) const; + bool IsSubtaskDownloadDisabled(u16 entry_index) const; + bool IsValidSubtask(u16 entry_index, u8 subtask_id) const; std::string GetVFFContentName(u16 entry_index, std::optional subtask_id) const; std::string GetDownloadURL(u16 entry_index, std::optional subtask_id) const; std::string GetVFFPath(u16 entry_index) const; WC24PubkMod GetWC24PubkMod(u16 entry_index) const; + u64 GetNextDownloadTime(u16 record_index) const; + u64 GetDownloadMargin(u16 entry_index) const; + void SetNextDownloadTime(u16 record_index, u64 value, std::optional subtask_id); + u64 GetRetryTime(u16 entry_index) const; + u64 GetLastSubtaskDownloadTime(u16 entry_index, u8 subtask_id) const; + u32 Magic() const; void SetMagic(u32 magic); @@ -46,10 +56,12 @@ public: private: static constexpr u32 DL_LIST_MAGIC = 0x5763446C; // WcDl static constexpr u32 MAX_SUBENTRIES = 32; + static constexpr u64 SECONDS_PER_MINUTE = 60; + static constexpr u64 MINUTES_PER_DAY = 1440; enum EntryType : u8 { - UNK = 1, + SUBTASK = 1, MAIL, CHANNEL_CONTENT, UNUSED = 0xff @@ -91,15 +103,14 @@ private: u16 padding1; u16 remaining_downloads; u16 error_count; - u16 dl_frequency; - u16 dl_frequency_when_err; + u16 dl_margin; + u16 retry_frequency; s32 error_code; u8 subtask_id; u8 subtask_type; - u8 subtask_flags; - u8 padding2; + u16 subtask_flags; u32 subtask_bitmask; - s32 unknown2; + u32 server_dl_interval; u32 dl_timestamp; // Last DL time u32 subtask_timestamps[32]; char dl_url[236]; diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp index 6bf7d6b766..833bc5707f 100644 --- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp +++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.cpp @@ -23,6 +23,7 @@ #include "Core/CommonTitles.h" #include "Core/HW/Memmap.h" #include "Core/IOS/FS/FileSystem.h" +#include "Core/IOS/Network/KD/NetKDTime.h" #include "Core/IOS/Network/KD/VFF/VFFUtil.h" #include "Core/IOS/Network/Socket.h" #include "Core/IOS/Uids.h" @@ -239,8 +240,20 @@ void NetKDRequestDevice::SchedulerWorker(const SchedulerEvent event) { if (event == SchedulerEvent::Download) { - // TODO: Implement downloader part of scheduler - return; + u16 entry_index{}; + std::optional subtask_id{}; + NWC24::ErrorCode code = DetermineDownloadTask(&entry_index, &subtask_id); + if (code != NWC24::WC24_OK) + { + LogError(ErrorType::KD_Download, code); + return; + } + + code = KDDownload(entry_index, subtask_id); + if (code != NWC24::WC24_OK) + { + LogError(ErrorType::KD_Download, code); + } } else { @@ -400,9 +413,109 @@ NWC24::ErrorCode NetKDRequestDevice::KDCheckMail(u32* mail_flag, u32* interval) return NWC24::WC24_OK; } +NWC24::ErrorCode NetKDRequestDevice::DetermineDownloadTask(u16* entry_index, + std::optional* subtask_id) const +{ + // As the scheduler does not tell us which entry to download, we must determine that. + // A correct entry is one that hasn't been downloaded the longest compared to other entries. + // We first need current UTC. + const auto time_device = + std::static_pointer_cast(GetIOS()->GetDeviceByName("/dev/net/kd/time")); + const u64 current_utc = time_device->GetAdjustedUTC(); + u64 lowest_timestamp = std::numeric_limits::max(); + + for (u16 i = 0; i < static_cast(NWC24::NWC24Dl::MAX_ENTRIES); i++) + { + if (!m_dl_list.DoesEntryExist(i)) + continue; + + if (m_dl_list.SkipSchedulerDownload(i)) + continue; + + const u64 next_dl_time = m_dl_list.GetNextDownloadTime(i); + + // First determine if UTC is greater than the next download time. + if (current_utc < next_dl_time) + continue; + + // If this task's next download time is less than the lowest_timestamp, this is the task we + // want. However, we must determine if this has a subtask and wants to be downloaded. + if (next_dl_time < lowest_timestamp) + { + if (m_dl_list.HasSubtask(i)) + { + NWC24::ErrorCode code = DetermineSubtask(i, subtask_id); + if (code != NWC24::WC24_OK) + { + // No valid subtask found or downloading is disabled. + continue; + } + } + + lowest_timestamp = next_dl_time; + *entry_index = i; + } + } + + // Determine if we actually found an entry to download. + if (lowest_timestamp == std::numeric_limits::max()) + return NWC24::WC24_ERR_NOT_FOUND; + + return NWC24::WC24_OK; +} + +NWC24::ErrorCode NetKDRequestDevice::DetermineSubtask(u16 entry_index, + std::optional* subtask_id) const +{ + // Before we do anything, determine if this task wants to be downloaded + if (m_dl_list.IsSubtaskDownloadDisabled(entry_index)) + return NWC24::WC24_ERR_DISABLED; + + const auto time_device = + std::static_pointer_cast(GetIOS()->GetDeviceByName("/dev/net/kd/time")); + const u64 current_utc = time_device->GetAdjustedUTC(); + for (u8 i = 0; i < 32; i++) + { + if (!m_dl_list.IsValidSubtask(entry_index, i)) + continue; + + // Unlike DetermineDownloadTask, DetermineSubtask gets the first download time lower than UTC. + const u64 last_download_time = m_dl_list.GetLastSubtaskDownloadTime(entry_index, i); + if (last_download_time < current_utc) + { + *subtask_id = i; + return NWC24::WC24_OK; + } + } + + return NWC24::WC24_ERR_INVALID_VALUE; +} + NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index, const std::optional subtask_id) { + bool success = false; + Common::ScopeGuard state_guard([&] { + const auto time_device = + std::static_pointer_cast(GetIOS()->GetDeviceByName("/dev/net/kd/time")); + const u64 current_utc = time_device->GetAdjustedUTC(); + if (success) + { + // Set the next download time to the dl_margin + m_dl_list.SetNextDownloadTime( + entry_index, current_utc + m_dl_list.GetDownloadMargin(entry_index), subtask_id); + } + else + { + // Else set it to the retry margin + m_dl_list.SetNextDownloadTime(entry_index, current_utc + m_dl_list.GetRetryTime(entry_index), + subtask_id); + } + + // Finally flush + m_dl_list.WriteDlList(); + }); + std::vector file_data; // Content metadata @@ -492,8 +605,12 @@ NWC24::ErrorCode NetKDRequestDevice::KDDownload(const u16 entry_index, m_ios.GetFS(), file_data); if (reply != NWC24::WC24_OK) + { LogError(ErrorType::KD_Download, reply); + return reply; + } + success = true; return reply; } diff --git a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h index 4f7a780359..99130cec6b 100644 --- a/Source/Core/Core/IOS/Network/KD/NetKDRequest.h +++ b/Source/Core/Core/IOS/Network/KD/NetKDRequest.h @@ -85,6 +85,8 @@ private: void LogError(ErrorType error_type, s32 error_code); void SchedulerTimer(); void SchedulerWorker(SchedulerEvent event); + NWC24::ErrorCode DetermineDownloadTask(u16* entry_index, std::optional* subtask_id) const; + NWC24::ErrorCode DetermineSubtask(u16 entry_index, std::optional* subtask_id) const; static std::string GetValueFromCGIResponse(const std::string& response, const std::string& key); static constexpr std::array MAIL_CHECK_KEY = {0xce, 0x4c, 0xf2, 0x9a, 0x3d, 0x6b, 0xe1, diff --git a/Source/Core/Core/IOS/Network/KD/NetKDTime.h b/Source/Core/Core/IOS/Network/KD/NetKDTime.h index 025594c921..4f24c8b36a 100644 --- a/Source/Core/Core/IOS/Network/KD/NetKDTime.h +++ b/Source/Core/Core/IOS/Network/KD/NetKDTime.h @@ -18,15 +18,15 @@ public: std::optional IOCtl(const IOCtlRequest& request) override; + // Returns seconds since Wii epoch + // +/- any bias set from IOCTL_NW24_SET_UNIVERSAL_TIME + u64 GetAdjustedUTC() const; + private: // TODO: depending on CEXIIPL is a hack which I don't feel like // removing because the function itself is pretty hackish; // wait until I re-port my netplay rewrite - // Returns seconds since Wii epoch - // +/- any bias set from IOCTL_NW24_SET_UNIVERSAL_TIME - u64 GetAdjustedUTC() const; - // Store the difference between what the Wii thinks is UTC and // what the host OS thinks void SetAdjustedUTC(u64 wii_utc); From 7869abf0e6cd691d37ceb319a29aca4e96396897 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sun, 10 Sep 2023 03:27:53 +0200 Subject: [PATCH 097/120] Common/MemArena: Set MAP_NORESERVE in LazyMemoryRegion on Linux. --- Source/Core/Common/MemArenaUnix.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Source/Core/Common/MemArenaUnix.cpp b/Source/Core/Common/MemArenaUnix.cpp index 9bf3633ee2..83026f76b8 100644 --- a/Source/Core/Common/MemArenaUnix.cpp +++ b/Source/Core/Common/MemArenaUnix.cpp @@ -117,6 +117,12 @@ LazyMemoryRegion::~LazyMemoryRegion() Release(); } +#if !defined MAP_NORESERVE && (defined __FreeBSD__ || defined __OpenBSD__ || defined __NetBSD__) +// BSD does not implement MAP_NORESERVE, so define the flag to nothing. +// See https://reviews.freebsd.org/rS273250 +#define MAP_NORESERVE 0 +#endif + void* LazyMemoryRegion::Create(size_t size) { ASSERT(!m_memory); @@ -124,7 +130,8 @@ void* LazyMemoryRegion::Create(size_t size) if (size == 0) return nullptr; - void* memory = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + void* memory = + mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); if (memory == MAP_FAILED) { NOTICE_LOG_FMT(MEMMAP, "Memory allocation of {} bytes failed.", size); @@ -142,7 +149,7 @@ void LazyMemoryRegion::Clear() ASSERT(m_memory); void* new_memory = mmap(m_memory, m_size, PROT_READ | PROT_WRITE, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE | MAP_FIXED, -1, 0); ASSERT(new_memory == m_memory); } From fd9c970621032e4f5b0d7056b41eb370c386845e Mon Sep 17 00:00:00 2001 From: Frajo Haider Date: Tue, 18 Jul 2023 17:15:17 +0300 Subject: [PATCH 098/120] JitArm64/Jit64: Extend the fast lookup mmap-ed segment further to avoid needing to check the msr bits. And in order to avoid a double dereference in the dispatcher, directly store the normalEntry in the map. The index to the block map becomes ((((DR<<1) | IR) << 30) | (address >> 2)). This has been chosen since the msr bits change less often than the address, thus we keep nearby entries together. Also do not call the C dispatcher in case the assembly dispatcher didn't find a block, since it wouldn't find a block either due to the 1:1 mapping, except when falling back to the non shm segment lookup table. --- Source/Core/Core/PowerPC/Jit64/JitAsm.cpp | 77 ++++++++------- Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp | 50 +++++----- .../Core/Core/PowerPC/JitCommon/JitCache.cpp | 93 ++++++++++++++----- Source/Core/Core/PowerPC/JitCommon/JitCache.h | 22 ++--- 4 files changed, 149 insertions(+), 93 deletions(-) diff --git a/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp b/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp index 844764d1d5..ef667cce86 100644 --- a/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp +++ b/Source/Core/Core/PowerPC/Jit64/JitAsm.cpp @@ -113,15 +113,22 @@ void Jit64AsmRoutineManager::Generate() const bool assembly_dispatcher = true; if (assembly_dispatcher) { - if (m_jit.GetBlockCache()->GetFastBlockMap()) + if (m_jit.GetBlockCache()->GetEntryPoints()) { - u64 icache = reinterpret_cast(m_jit.GetBlockCache()->GetFastBlockMap()); - MOV(32, R(RSCRATCH), PPCSTATE(pc)); + MOV(32, R(RSCRATCH2), PPCSTATE(msr)); + AND(32, R(RSCRATCH2), Imm32(JitBaseBlockCache::JIT_CACHE_MSR_MASK)); + SHL(64, R(RSCRATCH2), Imm8(28)); + MOV(32, R(RSCRATCH_EXTRA), PPCSTATE(pc)); + OR(64, R(RSCRATCH_EXTRA), R(RSCRATCH2)); + + u64 icache = reinterpret_cast(m_jit.GetBlockCache()->GetEntryPoints()); MOV(64, R(RSCRATCH2), Imm64(icache)); - // Each 4-byte offset of the PC register corresponds to a 8-byte offset - // in the lookup table due to host pointers being 8-bytes long. - MOV(64, R(RSCRATCH), MComplex(RSCRATCH2, RSCRATCH, SCALE_2, 0)); + // The entry points map is indexed by ((msrBits << 26) | (address >> 2)). + // The map contains 8 byte 64-bit pointers and that means we need to shift + // msr left by 29 bits and address left by 1 bit to get the correct offset + // in the map. + MOV(64, R(RSCRATCH), MComplex(RSCRATCH2, RSCRATCH_EXTRA, SCALE_2, 0)); } else { @@ -146,49 +153,57 @@ void Jit64AsmRoutineManager::Generate() // Check if we found a block. TEST(64, R(RSCRATCH), R(RSCRATCH)); FixupBranch not_found = J_CC(CC_Z); + FixupBranch state_mismatch; - // Check block.msrBits. - MOV(32, R(RSCRATCH2), PPCSTATE(msr)); - AND(32, R(RSCRATCH2), Imm32(JitBaseBlockCache::JIT_CACHE_MSR_MASK)); - - if (m_jit.GetBlockCache()->GetFastBlockMap()) - { - CMP(32, R(RSCRATCH2), MDisp(RSCRATCH, static_cast(offsetof(JitBlockData, msrBits)))); - } - else + if (!m_jit.GetBlockCache()->GetEntryPoints()) { + // Check block.msrBits. + MOV(32, R(RSCRATCH2), PPCSTATE(msr)); + AND(32, R(RSCRATCH2), Imm32(JitBaseBlockCache::JIT_CACHE_MSR_MASK)); // Also check the block.effectiveAddress SHL(64, R(RSCRATCH2), Imm8(32)); // RSCRATCH_EXTRA still has the PC. OR(64, R(RSCRATCH2), R(RSCRATCH_EXTRA)); CMP(64, R(RSCRATCH2), MDisp(RSCRATCH, static_cast(offsetof(JitBlockData, effectiveAddress)))); + + state_mismatch = J_CC(CC_NE); + // Success; branch to the block we found. + JMPptr(MDisp(RSCRATCH, static_cast(offsetof(JitBlockData, normalEntry)))); + } + else + { + // Success; branch to the block we found. + JMPptr(R(RSCRATCH)); } - FixupBranch state_mismatch = J_CC(CC_NE); - - // Success; branch to the block we found. - JMPptr(MDisp(RSCRATCH, static_cast(offsetof(JitBlockData, normalEntry)))); - SetJumpTarget(not_found); - SetJumpTarget(state_mismatch); + if (!m_jit.GetBlockCache()->GetEntryPoints()) + { + SetJumpTarget(state_mismatch); + } // Failure, fallback to the C++ dispatcher for calling the JIT. } - // Ok, no block, let's call the slow dispatcher - ABI_PushRegistersAndAdjustStack({}, 0); - MOV(64, R(ABI_PARAM1), Imm64(reinterpret_cast(&m_jit))); - ABI_CallFunction(JitBase::Dispatch); - ABI_PopRegistersAndAdjustStack({}, 0); + // There is no point in calling the dispatcher in the fast lookup table + // case, since the assembly dispatcher would already have found a block. + if (!assembly_dispatcher || !m_jit.GetBlockCache()->GetEntryPoints()) + { + // Ok, no block, let's call the slow dispatcher + ABI_PushRegistersAndAdjustStack({}, 0); + MOV(64, R(ABI_PARAM1), Imm64(reinterpret_cast(&m_jit))); + ABI_CallFunction(JitBase::Dispatch); + ABI_PopRegistersAndAdjustStack({}, 0); - TEST(64, R(ABI_RETURN), R(ABI_RETURN)); - FixupBranch no_block_available = J_CC(CC_Z); + TEST(64, R(ABI_RETURN), R(ABI_RETURN)); + FixupBranch no_block_available = J_CC(CC_Z); - // Jump to the block - JMPptr(R(ABI_RETURN)); + // Jump to the block + JMPptr(R(ABI_RETURN)); - SetJumpTarget(no_block_available); + SetJumpTarget(no_block_available); + } // We reset the stack because Jit might clear the code cache. // Also if we are in the middle of disabling BLR optimization on windows diff --git a/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp b/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp index bc4c77255e..5ff59e8d43 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/JitAsm.cpp @@ -97,32 +97,21 @@ void JitArm64::GenerateAsm() if (assembly_dispatcher) { - if (GetBlockCache()->GetFastBlockMap()) + if (GetBlockCache()->GetEntryPoints()) { // Check if there is a block - ARM64Reg pc_masked = ARM64Reg::X25; - ARM64Reg cache_base = ARM64Reg::X24; + ARM64Reg pc_and_msr = ARM64Reg::X25; + ARM64Reg cache_base = ARM64Reg::X27; ARM64Reg block = ARM64Reg::X30; - LSL(pc_masked, DISPATCHER_PC, 1); - MOVP2R(cache_base, GetBlockCache()->GetFastBlockMap()); - LDR(block, cache_base, pc_masked); + LDR(IndexType::Unsigned, EncodeRegTo32(pc_and_msr), PPC_REG, PPCSTATE_OFF(msr)); + MOVP2R(cache_base, GetBlockCache()->GetEntryPoints()); + // The entry points map is indexed by ((msrBits << 26) | (address >> 2)). + UBFIZ(pc_and_msr, pc_and_msr, 26, 6); + BFXIL(pc_and_msr, EncodeRegTo64(DISPATCHER_PC), 2, 30); + LDR(block, cache_base, ArithOption(pc_and_msr, true)); FixupBranch not_found = CBZ(block); - - // b.msrBits != msr - ARM64Reg msr = ARM64Reg::W27; - ARM64Reg msr2 = ARM64Reg::W24; - LDR(IndexType::Unsigned, msr, PPC_REG, PPCSTATE_OFF(msr)); - AND(msr, msr, LogicalImm(JitBaseBlockCache::JIT_CACHE_MSR_MASK, 32)); - LDR(IndexType::Unsigned, msr2, block, offsetof(JitBlockData, msrBits)); - CMP(msr, msr2); - - FixupBranch msr_missmatch = B(CC_NEQ); - - // return blocks[block_num].normalEntry; - LDR(IndexType::Unsigned, block, block, offsetof(JitBlockData, normalEntry)); BR(block); SetJumpTarget(not_found); - SetJumpTarget(msr_missmatch); } else { @@ -160,18 +149,25 @@ void JitArm64::GenerateAsm() } } - // Call C version of Dispatch(). STR(IndexType::Unsigned, DISPATCHER_PC, PPC_REG, PPCSTATE_OFF(pc)); - MOVP2R(ARM64Reg::X8, reinterpret_cast(&JitBase::Dispatch)); - MOVP2R(ARM64Reg::X0, this); - BLR(ARM64Reg::X8); - FixupBranch no_block_available = CBZ(ARM64Reg::X0); + // There is no point in calling the dispatcher in the fast lookup table + // case, since the assembly dispatcher would already have found a block. + if (!assembly_dispatcher || !GetBlockCache()->GetEntryPoints()) + { + // Call C version of Dispatch(). + MOVP2R(ARM64Reg::X8, reinterpret_cast(&JitBase::Dispatch)); + MOVP2R(ARM64Reg::X0, this); + BLR(ARM64Reg::X8); - BR(ARM64Reg::X0); + FixupBranch no_block_available = CBZ(ARM64Reg::X0); + + BR(ARM64Reg::X0); + + SetJumpTarget(no_block_available); + } // Call JIT - SetJumpTarget(no_block_available); ResetStack(); MOVP2R(ARM64Reg::X0, this); MOV(ARM64Reg::W1, DISPATCHER_PC); diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp index 95fad45bde..c8dddbc4ae 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp @@ -43,14 +43,10 @@ void JitBaseBlockCache::Init() Common::JitRegister::Init(Config::Get(Config::MAIN_PERF_MAP_DIR)); #ifdef _ARCH_64 - m_fast_block_map = reinterpret_cast(m_block_map_arena.Create(FAST_BLOCK_MAP_SIZE)); + m_entry_points_ptr = reinterpret_cast(m_entry_points_arena.Create(FAST_BLOCK_MAP_SIZE)); #else - m_fast_block_map = nullptr; + m_entry_points_ptr = nullptr; #endif - if (m_fast_block_map) - m_fast_block_map_ptr = m_fast_block_map; - else - m_fast_block_map_ptr = m_fast_block_map_fallback.data(); Clear(); } @@ -59,7 +55,7 @@ void JitBaseBlockCache::Shutdown() { Common::JitRegister::Shutdown(); - m_block_map_arena.Release(); + m_entry_points_arena.Release(); } // This clears the JIT cache. It's called from JitCache.cpp when the JIT cache @@ -82,8 +78,8 @@ void JitBaseBlockCache::Clear() valid_block.ClearAll(); - if (m_fast_block_map) - m_block_map_arena.Clear(); + if (m_entry_points_ptr) + m_entry_points_arena.Clear(); } void JitBaseBlockCache::Reset() @@ -92,9 +88,9 @@ void JitBaseBlockCache::Reset() Init(); } -JitBlock** JitBaseBlockCache::GetFastBlockMap() +u8** JitBaseBlockCache::GetEntryPoints() { - return m_fast_block_map; + return m_entry_points_ptr; } JitBlock** JitBaseBlockCache::GetFastBlockMapFallback() @@ -123,8 +119,11 @@ JitBlock* JitBaseBlockCache::AllocateBlock(u32 em_address) void JitBaseBlockCache::FinalizeBlock(JitBlock& block, bool block_link, const std::set& physical_addresses) { - size_t index = FastLookupIndexForAddress(block.effectiveAddress); - m_fast_block_map_ptr[index] = █ + size_t index = FastLookupIndexForAddress(block.effectiveAddress, block.msrBits); + if (m_entry_points_ptr) + m_entry_points_ptr[index] = block.normalEntry; + else + m_fast_block_map_fallback[index] = █ block.fast_block_map_index = index; block.physical_addresses = physical_addresses; @@ -187,7 +186,28 @@ JitBlock* JitBaseBlockCache::GetBlockFromStartAddress(u32 addr, u32 msr) const u8* JitBaseBlockCache::Dispatch() { const auto& ppc_state = m_jit.m_ppc_state; - JitBlock* block = m_fast_block_map_ptr[FastLookupIndexForAddress(ppc_state.pc)]; + if (m_entry_points_ptr) + { + u8* entry_point = + m_entry_points_ptr[FastLookupIndexForAddress(ppc_state.pc, ppc_state.msr.Hex)]; + if (entry_point) + { + return entry_point; + } + else + { + JitBlock* block = + MoveBlockIntoFastCache(ppc_state.pc, ppc_state.msr.Hex & JIT_CACHE_MSR_MASK); + + if (!block) + return nullptr; + + return block->normalEntry; + } + } + + JitBlock* block = + m_fast_block_map_fallback[FastLookupIndexForAddress(ppc_state.pc, ppc_state.msr.Hex)]; if (!block || block->effectiveAddress != ppc_state.pc || block->msrBits != (ppc_state.msr.Hex & JIT_CACHE_MSR_MASK)) @@ -408,8 +428,20 @@ void JitBaseBlockCache::UnlinkBlock(const JitBlock& block) void JitBaseBlockCache::DestroyBlock(JitBlock& block) { - if (m_fast_block_map_ptr[block.fast_block_map_index] == &block) - m_fast_block_map_ptr[block.fast_block_map_index] = nullptr; + if (m_entry_points_ptr) + { + if (m_entry_points_ptr[block.fast_block_map_index] == block.normalEntry) + { + m_entry_points_ptr[block.fast_block_map_index] = nullptr; + } + } + else + { + if (m_fast_block_map_fallback[block.fast_block_map_index] == &block) + { + m_fast_block_map_fallback[block.fast_block_map_index] = nullptr; + } + } UnlinkBlock(block); @@ -436,22 +468,37 @@ JitBlock* JitBaseBlockCache::MoveBlockIntoFastCache(u32 addr, u32 msr) return nullptr; // Drop old fast block map entry - if (m_fast_block_map_ptr[block->fast_block_map_index] == block) - m_fast_block_map_ptr[block->fast_block_map_index] = nullptr; + if (m_entry_points_ptr) + { + if (m_entry_points_ptr[block->fast_block_map_index] == block->normalEntry) + { + m_entry_points_ptr[block->fast_block_map_index] = nullptr; + } + } + else + { + if (m_fast_block_map_fallback[block->fast_block_map_index] == block) + { + m_fast_block_map_fallback[block->fast_block_map_index] = nullptr; + } + } // And create a new one - size_t index = FastLookupIndexForAddress(addr); - m_fast_block_map_ptr[index] = block; + size_t index = FastLookupIndexForAddress(addr, msr); + if (m_entry_points_ptr) + m_entry_points_ptr[index] = block->normalEntry; + else + m_fast_block_map_fallback[index] = block; block->fast_block_map_index = index; return block; } -size_t JitBaseBlockCache::FastLookupIndexForAddress(u32 address) +size_t JitBaseBlockCache::FastLookupIndexForAddress(u32 address, u32 msr) { - if (m_fast_block_map) + if (m_entry_points_ptr) { - return address >> 2; + return ((msr & JIT_CACHE_MSR_MASK) << 26) | (address >> 2); } else { diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.h b/Source/Core/Core/PowerPC/JitCommon/JitCache.h index cf6f785d98..e7978f2058 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.h @@ -133,8 +133,8 @@ public: static constexpr u32 JIT_CACHE_MSR_MASK = 0x30; // The value for the map is determined like this: - // ((4 GB guest memory space) / (4 bytes per address)) * sizeof(JitBlock*) - static constexpr u64 FAST_BLOCK_MAP_SIZE = 0x2'0000'0000; + // ((4 GB guest memory space) / (4 bytes per address) * sizeof(JitBlock*)) * (4 for 2 bits of msr) + static constexpr u64 FAST_BLOCK_MAP_SIZE = 0x8'0000'0000; static constexpr u32 FAST_BLOCK_MAP_FALLBACK_ELEMENTS = 0x10000; static constexpr u32 FAST_BLOCK_MAP_FALLBACK_MASK = FAST_BLOCK_MAP_FALLBACK_ELEMENTS - 1; @@ -147,7 +147,7 @@ public: void Reset(); // Code Cache - JitBlock** GetFastBlockMap(); + u8** GetEntryPoints(); JitBlock** GetFastBlockMapFallback(); void RunOnBlocks(std::function f); @@ -188,7 +188,7 @@ private: JitBlock* MoveBlockIntoFastCache(u32 em_address, u32 msr); // Fast but risky block lookup based on fast_block_map. - size_t FastLookupIndexForAddress(u32 address); + size_t FastLookupIndexForAddress(u32 address, u32 msr); // links_to hold all exit points of all valid blocks in a reverse way. // It is used to query all blocks which links to an address. @@ -208,16 +208,14 @@ private: // It is used to provide a fast way to query if no icache invalidation is needed. ValidBlockBitSet valid_block; - // This array is indexed with the shifted PC and likely holds the correct block id. - // This is used as a fast cache of block_map used in the assembly dispatcher. - // It is implemented via a shm segment using m_block_map_arena. - JitBlock** m_fast_block_map = 0; - Common::LazyMemoryRegion m_block_map_arena; + // This contains the entry points for each block. + // It is used by the assembly dispatcher to quickly + // know where to jump based on pc and msr bits. + Common::LazyMemoryRegion m_entry_points_arena; + u8** m_entry_points_ptr = 0; - // An alternative for the above fast_block_map but without a shm segment + // An alternative for the above but without a shm segment // in case the shm memory region couldn't be allocated. std::array m_fast_block_map_fallback{}; // start_addr & mask -> number - - JitBlock** m_fast_block_map_ptr = 0; }; From aa4de6516f8f80f9668cf33175ed8d0989c32713 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sat, 16 Sep 2023 15:52:13 +0200 Subject: [PATCH 099/120] Common: Fix HttpRequest::GetLastResponseCode stack corruption Should fix https://bugs.dolphin-emu.org/issues/13353. --- Source/Core/Common/HttpRequest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/Common/HttpRequest.cpp b/Source/Core/Common/HttpRequest.cpp index 3dc0f1d328..afc6271542 100644 --- a/Source/Core/Common/HttpRequest.cpp +++ b/Source/Core/Common/HttpRequest.cpp @@ -159,9 +159,9 @@ bool HttpRequest::Impl::IsValid() const s32 HttpRequest::Impl::GetLastResponseCode() { - s32 response_code{}; + long response_code{}; curl_easy_getinfo(m_curl.get(), CURLINFO_RESPONSE_CODE, &response_code); - return response_code; + return static_cast(response_code); } void HttpRequest::Impl::SetCookies(const std::string& cookies) From f13b29196d25ba8c36f40903ad5d025b09be9ba0 Mon Sep 17 00:00:00 2001 From: Charles Lombardo Date: Wed, 20 Sep 2023 14:12:22 -0400 Subject: [PATCH 100/120] Android: Use custom image loader for game covers This fixes a bug where custom cover loading was initiated but would finish by the time another image view would be in the place of the previous one. --- .../dolphinemu/adapters/GameAdapter.kt | 17 +-- .../dolphinemu/adapters/GameRowPresenter.kt | 17 +-- .../dolphinemu/ui/main/TvMainActivity.kt | 2 +- .../ui/platform/PlatformGamesFragment.kt | 2 +- .../dolphinemu/dolphinemu/utils/CoilUtils.kt | 113 ++++++++++++------ 5 files changed, 82 insertions(+), 69 deletions(-) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt index 520c96d812..c1ef9c2b8f 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameAdapter.kt @@ -25,7 +25,7 @@ import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting import org.dolphinemu.dolphinemu.utils.CoilUtils import java.util.ArrayList -class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapter(), +class GameAdapter : RecyclerView.Adapter(), View.OnClickListener, OnLongClickListener { private var mGameFiles: List = ArrayList() @@ -72,20 +72,7 @@ class GameAdapter(private val mActivity: FragmentActivity) : RecyclerView.Adapte binding.textGameCaption.visibility = View.GONE } } - - mActivity.lifecycleScope.launch { - withContext(Dispatchers.IO) { - val customCoverUri = CoilUtils.findCustomCover(gameFile) - withContext(Dispatchers.Main) { - CoilUtils.loadGameCover( - holder, - holder.binding.imageGameScreen, - gameFile, - customCoverUri - ) - } - } - } + CoilUtils.loadGameCover(holder, holder.binding.imageGameScreen, gameFile) val animateIn = AnimationUtils.loadAnimation(context, R.anim.anim_card_game_in) animateIn.fillAfter = true diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt index b355dc8f6d..754c089876 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/adapters/GameRowPresenter.kt @@ -24,7 +24,7 @@ import org.dolphinemu.dolphinemu.utils.CoilUtils * The Leanback library / docs call this a Presenter, but it works very * similarly to a RecyclerView.Adapter. */ -class GameRowPresenter(private val mActivity: FragmentActivity) : Presenter() { +class GameRowPresenter : Presenter() { override fun onCreateViewHolder(parent: ViewGroup): ViewHolder { // Create a new view. @@ -69,20 +69,7 @@ class GameRowPresenter(private val mActivity: FragmentActivity) : Presenter() { holder.cardParent.contentText = gameFile.getCompany() } } - - mActivity.lifecycleScope.launch { - withContext(Dispatchers.IO) { - val customCoverUri = CoilUtils.findCustomCover(gameFile) - withContext(Dispatchers.Main) { - CoilUtils.loadGameCover( - null, - holder.imageScreenshot, - gameFile, - customCoverUri - ) - } - } - } + CoilUtils.loadGameCover(null, holder.imageScreenshot, gameFile) } override fun onUnbindViewHolder(viewHolder: ViewHolder) { diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt index 79ec2f2943..9beec74fc3 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/main/TvMainActivity.kt @@ -268,7 +268,7 @@ class TvMainActivity : FragmentActivity(), MainView, OnRefreshListener { } // Create an adapter for this row. - val row = ArrayObjectAdapter(GameRowPresenter(this)) + val row = ArrayObjectAdapter(GameRowPresenter()) row.addAll(0, gameFiles) // Keep a reference to the row in case we need to refresh it. diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt index 5f1139da8a..c23eeb4fe0 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/platform/PlatformGamesFragment.kt @@ -37,7 +37,7 @@ class PlatformGamesFragment : Fragment(), PlatformGamesView { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { swipeRefresh = binding.swipeRefresh - val gameAdapter = GameAdapter(requireActivity()) + val gameAdapter = GameAdapter() gameAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt index dd2e2e68fb..350306b572 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/CoilUtils.kt @@ -2,11 +2,22 @@ package org.dolphinemu.dolphinemu.utils +import android.graphics.drawable.Drawable import android.net.Uri import android.view.View import android.widget.ImageView -import coil.load -import coil.target.ImageViewTarget +import coil.ImageLoader +import coil.decode.DataSource +import coil.executeBlocking +import coil.fetch.DrawableResult +import coil.fetch.FetchResult +import coil.fetch.Fetcher +import coil.imageLoader +import coil.key.Keyer +import coil.memory.MemoryCache +import coil.request.ImageRequest +import coil.request.Options +import org.dolphinemu.dolphinemu.DolphinApplication import org.dolphinemu.dolphinemu.R import org.dolphinemu.dolphinemu.adapters.GameAdapter.GameViewHolder import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting @@ -14,47 +25,75 @@ import org.dolphinemu.dolphinemu.model.GameFile import java.io.File import java.io.FileNotFoundException +class GameCoverFetcher( + private val game: GameFile, + private val options: Options +) : Fetcher { + override suspend fun fetch(): FetchResult { + val customCoverUri = CoilUtils.findCustomCover(game) + val builder = ImageRequest.Builder(DolphinApplication.getAppContext()) + var dataSource = DataSource.DISK + val drawable: Drawable? = if (customCoverUri != null) { + val request = builder.data(customCoverUri).error(R.drawable.no_banner).build() + DolphinApplication.getAppContext().imageLoader.executeBlocking(request).drawable + } else if (BooleanSetting.MAIN_USE_GAME_COVERS.boolean) { + val request = builder.data( + CoverHelper.buildGameTDBUrl(game, CoverHelper.getRegion(game)) + ).error(R.drawable.no_banner).build() + dataSource = DataSource.NETWORK + DolphinApplication.getAppContext().imageLoader.executeBlocking(request).drawable + } else { + null + } + + return DrawableResult( + // In the case where the drawable is null, intentionally throw an NPE. This tells Coil + // to load the error drawable. + drawable = drawable!!, + isSampled = false, + dataSource = dataSource + ) + } + + class Factory : Fetcher.Factory { + override fun create(data: GameFile, options: Options, imageLoader: ImageLoader): Fetcher = + GameCoverFetcher(data, options) + } +} + +class GameCoverKeyer : Keyer { + override fun key(data: GameFile, options: Options): String = data.getGameId() + data.getPath() +} + object CoilUtils { + private val imageLoader = ImageLoader.Builder(DolphinApplication.getAppContext()) + .components { + add(GameCoverKeyer()) + add(GameCoverFetcher.Factory()) + } + .memoryCache { + MemoryCache.Builder(DolphinApplication.getAppContext()) + .maxSizePercent(0.25) + .build() + } + .build() + fun loadGameCover( gameViewHolder: GameViewHolder?, imageView: ImageView, - gameFile: GameFile, - customCoverUri: Uri? + gameFile: GameFile ) { imageView.scaleType = ImageView.ScaleType.FIT_CENTER - val imageTarget = ImageViewTarget(imageView) - if (customCoverUri != null) { - imageView.load(customCoverUri) { - error(R.drawable.no_banner) - target( - onSuccess = { success -> - disableInnerTitle(gameViewHolder) - imageTarget.drawable = success - }, - onError = { error -> - enableInnerTitle(gameViewHolder) - imageTarget.drawable = error - } - ) - } - } else if (BooleanSetting.MAIN_USE_GAME_COVERS.boolean) { - imageView.load(CoverHelper.buildGameTDBUrl(gameFile, CoverHelper.getRegion(gameFile))) { - error(R.drawable.no_banner) - target( - onSuccess = { success -> - disableInnerTitle(gameViewHolder) - imageTarget.drawable = success - }, - onError = { error -> - enableInnerTitle(gameViewHolder) - imageTarget.drawable = error - } - ) - } - } else { - imageView.load(R.drawable.no_banner) - enableInnerTitle(gameViewHolder) - } + val imageRequest = ImageRequest.Builder(imageView.context) + .data(gameFile) + .error(R.drawable.no_banner) + .target(imageView) + .listener( + onSuccess = { _, _ -> disableInnerTitle(gameViewHolder) }, + onError = { _, _ -> enableInnerTitle(gameViewHolder) } + ) + .build() + imageLoader.enqueue(imageRequest) } private fun enableInnerTitle(gameViewHolder: GameViewHolder?) { From 965283c2634de96338eb106369b5d6abf469dd40 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Wed, 20 Sep 2023 23:05:53 +0200 Subject: [PATCH 101/120] Update fmt to 10.1.1 and convert to submodule. --- .gitmodules | 3 + Externals/fmt/CMakeLists.txt | 388 +- Externals/fmt/CONTRIBUTING.md | 20 - Externals/fmt/ChangeLog.rst | 5255 ----------------- Externals/fmt/LICENSE.rst | 27 - Externals/fmt/README.rst | 531 -- Externals/fmt/exports.props | 2 +- Externals/fmt/fmt | 1 + Externals/fmt/fmt.vcxproj | 32 +- Externals/fmt/include/fmt/args.h | 234 - Externals/fmt/include/fmt/chrono.h | 2069 ------- Externals/fmt/include/fmt/color.h | 651 -- Externals/fmt/include/fmt/compile.h | 611 -- Externals/fmt/include/fmt/core.h | 3323 ----------- Externals/fmt/include/fmt/format-inl.h | 1723 ------ Externals/fmt/include/fmt/format.h | 4217 ------------- Externals/fmt/include/fmt/os.h | 478 -- Externals/fmt/include/fmt/ostream.h | 237 - Externals/fmt/include/fmt/printf.h | 640 -- Externals/fmt/include/fmt/ranges.h | 722 --- Externals/fmt/include/fmt/std.h | 171 - Externals/fmt/include/fmt/xchar.h | 229 - Externals/fmt/src/fmt.cc | 99 - Externals/fmt/src/format.cc | 47 - Externals/fmt/src/os.cc | 361 -- Externals/fmt/support/Android.mk | 15 - Externals/fmt/support/AndroidManifest.xml | 1 - Externals/fmt/support/C++.sublime-syntax | 2061 ------- Externals/fmt/support/README | 4 - Externals/fmt/support/Vagrantfile | 20 - Externals/fmt/support/bazel/.bazelrc | 1 - Externals/fmt/support/bazel/.bazelversion | 1 - Externals/fmt/support/bazel/BUILD.bazel | 28 - Externals/fmt/support/bazel/README.md | 73 - Externals/fmt/support/bazel/WORKSPACE.bazel | 1 - Externals/fmt/support/build-docs.py | 58 - Externals/fmt/support/build.gradle | 132 - Externals/fmt/support/cmake/FindSetEnv.cmake | 7 - Externals/fmt/support/cmake/JoinPaths.cmake | 26 - Externals/fmt/support/cmake/cxx14.cmake | 54 - .../fmt/support/cmake/fmt-config.cmake.in | 7 - Externals/fmt/support/cmake/fmt.pc.in | 11 - Externals/fmt/support/compute-powers.py | 53 - Externals/fmt/support/docopt.py | 581 -- Externals/fmt/support/manage.py | 303 - Externals/fmt/support/printable.py | 201 - Externals/fmt/support/rst2md.py | 159 - Externals/fmt/support/rtd/conf.py | 7 - Externals/fmt/support/rtd/index.rst | 2 - Externals/fmt/support/rtd/theme/layout.html | 17 - Externals/fmt/support/rtd/theme/theme.conf | 2 - 51 files changed, 22 insertions(+), 25874 deletions(-) delete mode 100644 Externals/fmt/CONTRIBUTING.md delete mode 100755 Externals/fmt/ChangeLog.rst delete mode 100755 Externals/fmt/LICENSE.rst delete mode 100755 Externals/fmt/README.rst create mode 160000 Externals/fmt/fmt delete mode 100644 Externals/fmt/include/fmt/args.h delete mode 100755 Externals/fmt/include/fmt/chrono.h delete mode 100755 Externals/fmt/include/fmt/color.h delete mode 100644 Externals/fmt/include/fmt/compile.h delete mode 100755 Externals/fmt/include/fmt/core.h delete mode 100755 Externals/fmt/include/fmt/format-inl.h delete mode 100755 Externals/fmt/include/fmt/format.h delete mode 100644 Externals/fmt/include/fmt/os.h delete mode 100755 Externals/fmt/include/fmt/ostream.h delete mode 100755 Externals/fmt/include/fmt/printf.h delete mode 100755 Externals/fmt/include/fmt/ranges.h delete mode 100644 Externals/fmt/include/fmt/std.h delete mode 100644 Externals/fmt/include/fmt/xchar.h delete mode 100644 Externals/fmt/src/fmt.cc delete mode 100755 Externals/fmt/src/format.cc delete mode 100644 Externals/fmt/src/os.cc delete mode 100755 Externals/fmt/support/Android.mk delete mode 100755 Externals/fmt/support/AndroidManifest.xml delete mode 100644 Externals/fmt/support/C++.sublime-syntax delete mode 100755 Externals/fmt/support/README delete mode 100644 Externals/fmt/support/Vagrantfile delete mode 100644 Externals/fmt/support/bazel/.bazelrc delete mode 100644 Externals/fmt/support/bazel/.bazelversion delete mode 100644 Externals/fmt/support/bazel/BUILD.bazel delete mode 100644 Externals/fmt/support/bazel/README.md delete mode 100644 Externals/fmt/support/bazel/WORKSPACE.bazel delete mode 100644 Externals/fmt/support/build-docs.py delete mode 100755 Externals/fmt/support/build.gradle delete mode 100755 Externals/fmt/support/cmake/FindSetEnv.cmake delete mode 100644 Externals/fmt/support/cmake/JoinPaths.cmake delete mode 100755 Externals/fmt/support/cmake/cxx14.cmake delete mode 100755 Externals/fmt/support/cmake/fmt-config.cmake.in delete mode 100755 Externals/fmt/support/cmake/fmt.pc.in delete mode 100755 Externals/fmt/support/compute-powers.py delete mode 100755 Externals/fmt/support/docopt.py delete mode 100755 Externals/fmt/support/manage.py delete mode 100644 Externals/fmt/support/printable.py delete mode 100755 Externals/fmt/support/rst2md.py delete mode 100755 Externals/fmt/support/rtd/conf.py delete mode 100755 Externals/fmt/support/rtd/index.rst delete mode 100755 Externals/fmt/support/rtd/theme/layout.html delete mode 100755 Externals/fmt/support/rtd/theme/theme.conf diff --git a/.gitmodules b/.gitmodules index 0e07cdfb67..bac18a1143 100644 --- a/.gitmodules +++ b/.gitmodules @@ -60,3 +60,6 @@ [submodule "Externals/curl/curl"] path = Externals/curl/curl url = https://github.com/curl/curl.git +[submodule "Externals/fmt/fmt"] + path = Externals/fmt/fmt + url = https://github.com/fmtlib/fmt.git diff --git a/Externals/fmt/CMakeLists.txt b/Externals/fmt/CMakeLists.txt index 4ea516f85b..2b4e30dc32 100755 --- a/Externals/fmt/CMakeLists.txt +++ b/Externals/fmt/CMakeLists.txt @@ -1,388 +1,2 @@ -cmake_minimum_required(VERSION 3.1...3.18) - -# Fallback for using newer policies on CMake <3.12. -if(${CMAKE_VERSION} VERSION_LESS 3.12) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) -endif() - -# Determine if fmt is built as a subproject (using add_subdirectory) -# or if it is the master project. -if (NOT DEFINED FMT_MASTER_PROJECT) - set(FMT_MASTER_PROJECT OFF) - if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) - set(FMT_MASTER_PROJECT ON) - message(STATUS "CMake version: ${CMAKE_VERSION}") - endif () -endif () - -# Joins arguments and places the results in ${result_var}. -function(join result_var) - set(result "") - foreach (arg ${ARGN}) - set(result "${result}${arg}") - endforeach () - set(${result_var} "${result}" PARENT_SCOPE) -endfunction() - -function(enable_module target) - if (MSVC) - set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc) - target_compile_options(${target} - PRIVATE /interface /ifcOutput ${BMI} - INTERFACE /reference fmt=${BMI}) - endif () - set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI}) - set_source_files_properties(${BMI} PROPERTIES GENERATED ON) -endfunction() - -include(CMakeParseArguments) - -# Sets a cache variable with a docstring joined from multiple arguments: -# set( ... CACHE ...) -# This allows splitting a long docstring for readability. -function(set_verbose) - # cmake_parse_arguments is broken in CMake 3.4 (cannot parse CACHE) so use - # list instead. - list(GET ARGN 0 var) - list(REMOVE_AT ARGN 0) - list(GET ARGN 0 val) - list(REMOVE_AT ARGN 0) - list(REMOVE_AT ARGN 0) - list(GET ARGN 0 type) - list(REMOVE_AT ARGN 0) - join(doc ${ARGN}) - set(${var} ${val} CACHE ${type} ${doc}) -endfunction() - -# Set the default CMAKE_BUILD_TYPE to Release. -# This should be done before the project command since the latter can set -# CMAKE_BUILD_TYPE itself (it does so for nmake). -if (FMT_MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE) - set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING - "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or " - "CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") -endif () - -project(FMT CXX) -include(GNUInstallDirs) -set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING - "Installation directory for include files, a relative path that " - "will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path.") - -option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF) -option(FMT_WERROR "Halt the compilation with an error on compiler warnings." - OFF) - -# Options that control generation of various targets. -option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT}) -option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT}) -option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT}) -option(FMT_FUZZ "Generate the fuzz target." OFF) -option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) -option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) -option(FMT_MODULE "Build a module instead of a traditional library." OFF) -option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF) - -set(FMT_CAN_MODULE OFF) -if (CMAKE_CXX_STANDARD GREATER 17 AND - # msvc 16.10-pre4 - MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035) - set(FMT_CAN_MODULE OFF) -endif () -if (NOT FMT_CAN_MODULE) - set(FMT_MODULE OFF) - message(STATUS "Module support is disabled.") -endif () -if (FMT_TEST AND FMT_MODULE) - # The tests require {fmt} to be compiled as traditional library - message(STATUS "Testing is incompatible with build mode 'module'.") -endif () -set(FMT_SYSTEM_HEADERS_ATTRIBUTE "") -if (FMT_SYSTEM_HEADERS) - set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM) -endif () - -# Get version from core.h -file(READ include/fmt/core.h core_h) -if (NOT core_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])") - message(FATAL_ERROR "Cannot get FMT_VERSION from core.h.") -endif () -# Use math to skip leading zeros if any. -math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1}) -math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2}) -math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3}) -join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}. - ${CPACK_PACKAGE_VERSION_PATCH}) -message(STATUS "Version: ${FMT_VERSION}") - -message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") - -if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -endif () - -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} - "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake") - -include(cxx14) -include(JoinPaths) - -list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index) -if (${index} GREATER -1) - # Use cxx_variadic_templates instead of more appropriate cxx_std_11 for - # compatibility with older CMake versions. - set(FMT_REQUIRED_FEATURES cxx_variadic_templates) -endif () -message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}") - -if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET) - set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING - "Preset for the export of private symbols") - set_property(CACHE CMAKE_CXX_VISIBILITY_PRESET PROPERTY STRINGS - hidden default) -endif () - -if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_VISIBILITY_INLINES_HIDDEN) - set_verbose(CMAKE_VISIBILITY_INLINES_HIDDEN ON CACHE BOOL - "Whether to add a compile flag to hide symbols of inline functions") -endif () - -if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") - set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic - -Wold-style-cast -Wundef - -Wredundant-decls -Wwrite-strings -Wpointer-arith - -Wcast-qual -Wformat=2 -Wmissing-include-dirs - -Wcast-align - -Wctor-dtor-privacy -Wdisabled-optimization - -Winvalid-pch -Woverloaded-virtual - -Wconversion -Wundef - -Wno-ctor-dtor-privacy -Wno-format-nonliteral) - if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6) - set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} - -Wno-dangling-else -Wno-unused-local-typedefs) - endif () - if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) - set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion - -Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast - -Wvector-operation-performance -Wsized-deallocation -Wshadow) - endif () - if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0) - set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2 - -Wnull-dereference -Wduplicated-cond) - endif () - set(WERROR_FLAG -Werror) -endif () - -if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion -Wundef - -Wdeprecated -Wweak-vtables -Wshadow - -Wno-gnu-zero-variadic-macro-arguments) - check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING) - if (HAS_NULLPTR_WARNING) - set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} - -Wzero-as-null-pointer-constant) - endif () - set(WERROR_FLAG -Werror) -endif () - -if (MSVC) - set(PEDANTIC_COMPILE_FLAGS /W3) - set(WERROR_FLAG /WX) -endif () - -if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio") - # If Microsoft SDK is installed create script run-msbuild.bat that - # calls SetEnv.cmd to set up build environment and runs msbuild. - # It is useful when building Visual Studio projects with the SDK - # toolchain rather than Visual Studio. - include(FindSetEnv) - if (WINSDK_SETENV) - set(MSBUILD_SETUP "call \"${WINSDK_SETENV}\"") - endif () - # Set FrameworkPathOverride to get rid of MSB3644 warnings. - join(netfxpath - "C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\" - ".NETFramework\\v4.0") - file(WRITE run-msbuild.bat " - ${MSBUILD_SETUP} - ${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*") -endif () - -function(add_headers VAR) - set(headers ${${VAR}}) - foreach (header ${ARGN}) - set(headers ${headers} include/fmt/${header}) - endforeach() - set(${VAR} ${headers} PARENT_SCOPE) -endfunction() - -# Define the fmt library, its includes and the needed defines. -add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h - format-inl.h os.h ostream.h printf.h ranges.h std.h - xchar.h) -if (FMT_MODULE) - set(FMT_SOURCES src/fmt.cc) -elseif (FMT_OS) - set(FMT_SOURCES src/format.cc src/os.cc) -else() - set(FMT_SOURCES src/format.cc) -endif () - -add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst) +add_subdirectory(fmt) dolphin_disable_warnings_msvc(fmt) -add_library(fmt::fmt ALIAS fmt) - -if (FMT_WERROR) - target_compile_options(fmt PRIVATE ${WERROR_FLAG}) -endif () -if (FMT_PEDANTIC) - target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS}) -endif () -if (FMT_MODULE) - enable_module(fmt) -endif () - -target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) - -target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC - $ - $) - -set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") - -set_target_properties(fmt PROPERTIES - VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} - PUBLIC_HEADER "${FMT_HEADERS}" - DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}") - -# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target -# property because it's not set by default. -set(FMT_LIB_NAME fmt) -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX}) -endif () - -if (BUILD_SHARED_LIBS) - target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED) -endif () -if (FMT_SAFE_DURATION_CAST) - target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST) -endif() - -add_library(fmt-header-only INTERFACE) -add_library(fmt::fmt-header-only ALIAS fmt-header-only) - -target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) -target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES}) - -target_include_directories(fmt-header-only ${FMT_SYSTEM_HEADERS_ATTRIBUTE} INTERFACE - $ - $) - -# Install targets. -if (FMT_INSTALL) - include(CMakePackageConfigHelpers) - set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING - "Installation directory for cmake files, a relative path that " - "will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute " - "path.") - set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake) - set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake) - set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc) - set(targets_export_name fmt-targets) - - set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING - "Installation directory for libraries, a relative path that " - "will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.") - - set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH - "Installation directory for pkgconfig (.pc) files, a relative " - "path that will be joined with ${CMAKE_INSTALL_PREFIX} or an " - "absolute path.") - - # Generate the version, config and target files into the build directory. - write_basic_package_version_file( - ${version_config} - VERSION ${FMT_VERSION} - COMPATIBILITY AnyNewerVersion) - - join_paths(libdir_for_pc_file "\${exec_prefix}" "${FMT_LIB_DIR}") - join_paths(includedir_for_pc_file "\${prefix}" "${FMT_INC_DIR}") - - configure_file( - "${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in" - "${pkgconfig}" - @ONLY) - configure_package_config_file( - ${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in - ${project_config} - INSTALL_DESTINATION ${FMT_CMAKE_DIR}) - - set(INSTALL_TARGETS fmt fmt-header-only) - - # Install the library and headers. - install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name} - LIBRARY DESTINATION ${FMT_LIB_DIR} - ARCHIVE DESTINATION ${FMT_LIB_DIR} - PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt" - FRAMEWORK DESTINATION "." - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) - - # Use a namespace because CMake provides better diagnostics for namespaced - # imported targets. - export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt:: - FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) - - # Install version, config and target files. - install( - FILES ${project_config} ${version_config} - DESTINATION ${FMT_CMAKE_DIR}) - install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} - NAMESPACE fmt::) - - install(FILES $ - DESTINATION ${FMT_LIB_DIR} OPTIONAL) - install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}") -endif () - -if (FMT_DOC) - add_subdirectory(doc) -endif () - -if (FMT_TEST) - enable_testing() - add_subdirectory(test) -endif () - -# Control fuzzing independent of the unit tests. -if (FMT_FUZZ) - add_subdirectory(test/fuzzing) - - # The FMT_FUZZ macro is used to prevent resource exhaustion in fuzzing - # mode and make fuzzing practically possible. It is similar to - # FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION but uses a different name to - # avoid interfering with fuzzing of projects that use {fmt}. - # See also https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode. - target_compile_definitions(fmt PUBLIC FMT_FUZZ) -endif () - -set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore) -if (FMT_MASTER_PROJECT AND EXISTS ${gitignore}) - # Get the list of ignored files from .gitignore. - file (STRINGS ${gitignore} lines) - list(REMOVE_ITEM lines /doc/html) - foreach (line ${lines}) - string(REPLACE "." "[.]" line "${line}") - string(REPLACE "*" ".*" line "${line}") - set(ignored_files ${ignored_files} "${line}$" "${line}/") - endforeach () - set(ignored_files ${ignored_files} - /.git /breathe /format-benchmark sphinx/ .buildinfo .doctrees) - - set(CPACK_SOURCE_GENERATOR ZIP) - set(CPACK_SOURCE_IGNORE_FILES ${ignored_files}) - set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION}) - set(CPACK_PACKAGE_NAME fmt) - set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst) - include(CPack) -endif () diff --git a/Externals/fmt/CONTRIBUTING.md b/Externals/fmt/CONTRIBUTING.md deleted file mode 100644 index b82f145069..0000000000 --- a/Externals/fmt/CONTRIBUTING.md +++ /dev/null @@ -1,20 +0,0 @@ -Contributing to {fmt} -===================== - -By submitting a pull request or a patch, you represent that you have the right -to license your contribution to the {fmt} project owners and the community, -agree that your contributions are licensed under the {fmt} license, and agree -to future changes to the licensing. - -All C++ code must adhere to [Google C++ Style Guide]( -https://google.github.io/styleguide/cppguide.html) with the following -exceptions: - -* Exceptions are permitted -* snake_case should be used instead of UpperCamelCase for function and type - names - -All documentation must adhere to the [Google Developer Documentation Style -Guide](https://developers.google.com/style). - -Thanks for contributing! diff --git a/Externals/fmt/ChangeLog.rst b/Externals/fmt/ChangeLog.rst deleted file mode 100755 index 4ebc5c7330..0000000000 --- a/Externals/fmt/ChangeLog.rst +++ /dev/null @@ -1,5255 +0,0 @@ -9.1.0 - 2022-08-27 ------------------- - -* ``fmt::formatted_size`` now works at compile time - (`#3026 `_). For example - (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - using namespace fmt::literals; - constexpr size_t n = fmt::formatted_size("{}"_cf, 42); - fmt::print("{}\n", n); // prints 2 - } - - Thanks `@marksantaniello (Mark Santaniello) - `_. - -* Fixed handling of invalid UTF-8 - (`#3038 `_, - `#3044 `_, - `#3056 `_). - Thanks `@phprus (Vladislav Shchapov) `_ and - `@skeeto (Christopher Wellons) `_. - -* Improved Unicode support in ``ostream`` overloads of ``print`` - (`#2994 `_, - `#3001 `_, - `#3025 `_). - Thanks `@dimztimz (Dimitrij Mijoski) `_. - -* Fixed handling of the sign specifier in localized formatting on systems with - 32-bit ``wchar_t`` (`#3041 `_). - -* Added support for wide streams to ``fmt::streamed`` - (`#2994 `_). - Thanks `@phprus (Vladislav Shchapov) `_. - -* Added the ``n`` specifier that disables the output of delimiters when - formatting ranges (`#2981 `_, - `#2983 `_). - For example (`godbolt `__): - - .. code:: c++ - - #include - #include - - int main() { - auto v = std::vector{1, 2, 3}; - fmt::print("{:n}\n", v); // prints 1, 2, 3 - } - - Thanks `@BRevzin (Barry Revzin) `_. - -* Worked around problematic ``std::string_view`` constructors introduced in - C++23 (`#3030 `_, - `#3050 `_). - Thanks `@strega-nil-ms (nicole mazzuca) `_. - -* Improve handling (exclusion) of recursive ranges - (`#2968 `_, - `#2974 `_). - Thanks `@Dani-Hub (Daniel Krügler) `_. - -* Improved error reporting in format string compilation - (`#3055 `_). - -* Improved the implementation of - `Dragonbox `_, the algorithm used for - the default floating-point formatting - (`#2984 `_). - Thanks `@jk-jeon (Junekey Jeon) `_. - -* Fixed issues with floating-point formatting on exotic platforms. - -* Improved the implementation of chrono formatting - (`#3010 `_). - Thanks `@phprus (Vladislav Shchapov) `_. - -* Improved documentation - (`#2966 `_, - `#3009 `_, - `#3020 `_, - `#3037 `_). - Thanks `@mwinterb `_, - `@jcelerier (Jean-Michaël Celerier) `_ - and `@remiburtin (Rémi Burtin) `_. - -* Improved build configuration - (`#2991 `_, - `#2995 `_, - `#3004 `_, - `#3007 `_, - `#3040 `_). - Thanks `@dimztimz (Dimitrij Mijoski) `_ and - `@hwhsu1231 (Haowei Hsu) `_. - -* Fixed various warnings and compilation issues - (`#2969 `_, - `#2971 `_, - `#2975 `_, - `#2982 `_, - `#2985 `_, - `#2988 `_, - `#3000 `_, - `#3006 `_, - `#3014 `_, - `#3015 `_, - `#3021 `_, - `#3023 `_, - `#3024 `_, - `#3029 `_, - `#3043 `_, - `#3052 `_, - `#3053 `_, - `#3054 `_). - Thanks `@h-friederich (Hannes Friederich) `_, - `@dimztimz (Dimitrij Mijoski) `_, - `@olupton (Olli Lupton) `_, - `@bernhardmgruber (Bernhard Manfred Gruber) - `_, - `@phprus (Vladislav Shchapov) `_. - -9.0.0 - 2022-07-04 ------------------- - -* Switched to the internal floating point formatter for all decimal presentation - formats. In particular this results in consistent rounding on all platforms - and removing the ``s[n]printf`` fallback for decimal FP formatting. - -* Compile-time floating point formatting no longer requires the header-only - mode. For example (`godbolt `__): - - .. code:: c++ - - #include - #include - - consteval auto compile_time_dtoa(double value) -> std::array { - auto result = std::array(); - fmt::format_to(result.data(), FMT_COMPILE("{}"), value); - return result; - } - - constexpr auto answer = compile_time_dtoa(0.42); - - works with the default settings. - -* Improved the implementation of - `Dragonbox `_, the algorithm used for - the default floating-point formatting - (`#2713 `_, - `#2750 `_). - Thanks `@jk-jeon (Junekey Jeon) `_. - -* Made ``fmt::to_string`` work with ``__float128``. This uses the internal - FP formatter and works even on system without ``__float128`` support in - ``[s]printf``. - -* Disabled automatic ``std::ostream`` insertion operator (``operator<<``) - discovery when ``fmt/ostream.h`` is included to prevent ODR violations. - You can get the old behavior by defining ``FMT_DEPRECATED_OSTREAM`` but this - will be removed in the next major release. Use ``fmt::streamed`` or - ``fmt::ostream_formatter`` to enable formatting via ``std::ostream`` instead. - -* Added ``fmt::ostream_formatter`` that can be used to write ``formatter`` - specializations that perform formatting via ``std::ostream``. - For example (`godbolt `__): - - .. code:: c++ - - #include - - struct date { - int year, month, day; - - friend std::ostream& operator<<(std::ostream& os, const date& d) { - return os << d.year << '-' << d.month << '-' << d.day; - } - }; - - template <> struct fmt::formatter : ostream_formatter {}; - - std::string s = fmt::format("The date is {}", date{2012, 12, 9}); - // s == "The date is 2012-12-9" - -* Added the ``fmt::streamed`` function that takes an object and formats it - via ``std::ostream``. - For example (`godbolt `__): - - .. code:: c++ - - #include - #include - - int main() { - fmt::print("Current thread id: {}\n", - fmt::streamed(std::this_thread::get_id())); - } - - Note that ``fmt/std.h`` provides a ``formatter`` specialization for - ``std::thread::id`` so you don't need to format it via ``std::ostream``. - -* Deprecated implicit conversions of unscoped enums to integers for consistency - with scoped enums. - -* Added an argument-dependent lookup based ``format_as`` extension API to - simplify formatting of enums. - -* Added experimental ``std::variant`` formatting support - (`#2941 `_). - For example (`godbolt `__): - - .. code:: c++ - - #include - #include - - int main() { - auto v = std::variant(42); - fmt::print("{}\n", v); - } - - prints:: - - variant(42) - - Thanks `@jehelset `_. - -* Added experimental ``std::filesystem::path`` formatting support - (`#2865 `_, - `#2902 `_, - `#2917 `_, - `#2918 `_). - For example (`godbolt `__): - - .. code:: c++ - - #include - #include - - int main() { - fmt::print("There is no place like {}.", std::filesystem::path("/home")); - } - - prints:: - - There is no place like "/home". - - Thanks `@phprus (Vladislav Shchapov) `_. - -* Added a ``std::thread::id`` formatter to ``fmt/std.h``. - For example (`godbolt `__): - - .. code:: c++ - - #include - #include - - int main() { - fmt::print("Current thread id: {}\n", std::this_thread::get_id()); - } - -* Added ``fmt::styled`` that applies a text style to an individual argument - (`#2793 `_). - For example (`godbolt `__): - - .. code:: c++ - - #include - #include - - int main() { - auto now = std::chrono::system_clock::now(); - fmt::print( - "[{}] {}: {}\n", - fmt::styled(now, fmt::emphasis::bold), - fmt::styled("error", fg(fmt::color::red)), - "something went wrong"); - } - - prints - - .. image:: https://user-images.githubusercontent.com/576385/ - 175071215-12809244-dab0-4005-96d8-7cd911c964d5.png - - Thanks `@rbrugo (Riccardo Brugo) `_. - -* Made ``fmt::print`` overload for text styles correctly handle UTF-8 - (`#2681 `_, - `#2701 `_). - Thanks `@AlexGuteniev (Alex Guteniev) `_. - -* Fixed Unicode handling when writing to an ostream. - -* Added support for nested specifiers to range formatting - (`#2673 `_). - For example (`godbolt `__): - - .. code:: c++ - - #include - #include - - int main() { - fmt::print("{::#x}\n", std::vector{10, 20, 30}); - } - - prints ``[0xa, 0x14, 0x1e]``. - - Thanks `@BRevzin (Barry Revzin) `_. - -* Implemented escaping of wide strings in ranges - (`#2904 `_). - Thanks `@phprus (Vladislav Shchapov) `_. - -* Added support for ranges with ``begin`` / ``end`` found via the - argument-dependent lookup - (`#2807 `_). - Thanks `@rbrugo (Riccardo Brugo) `_. - -* Fixed formatting of certain kinds of ranges of ranges - (`#2787 `_). - Thanks `@BRevzin (Barry Revzin) `_. - -* Fixed handling of maps with element types other than ``std::pair`` - (`#2944 `_). - Thanks `@BrukerJWD (Jonathan W) `_. - -* Made tuple formatter enabled only if elements are formattable - (`#2939 `_, - `#2940 `_). - Thanks `@jehelset `_. - -* Made ``fmt::join`` compatible with format string compilation - (`#2719 `_, - `#2720 `_). - Thanks `@phprus (Vladislav Shchapov) `_. - -* Made compile-time checks work with named arguments of custom types and - ``std::ostream`` ``print`` overloads - (`#2816 `_, - `#2817 `_, - `#2819 `_). - Thanks `@timsong-cpp `_. - -* Removed ``make_args_checked`` because it is no longer needed for compile-time - checks (`#2760 `_). - Thanks `@phprus (Vladislav Shchapov) `_. - -* Removed the following deprecated APIs: ``_format``, ``arg_join``, - the ``format_to`` overload that takes a memory buffer, - ``[v]fprintf`` that takes an ``ostream``. - -* Removed the deprecated implicit conversion of ``[const] signed char*`` and - ``[const] unsigned char*`` to C strings. - -* Removed the deprecated ``fmt/locale.h``. - -* Replaced the deprecated ``fileno()`` with ``descriptor()`` in - ``buffered_file``. - -* Moved ``to_string_view`` to the ``detail`` namespace since it's an - implementation detail. - -* Made access mode of a created file consistent with ``fopen`` by setting - ``S_IWGRP`` and ``S_IWOTH`` - (`#2733 `_). - Thanks `@arogge (Andreas Rogge) `_. - -* Removed a redundant buffer resize when formatting to ``std::ostream`` - (`#2842 `_, - `#2843 `_). - Thanks `@jcelerier (Jean-Michaël Celerier) `_. - -* Made precision computation for strings consistent with width - (`#2888 `_). - -* Fixed handling of locale separators in floating point formatting - (`#2830 `_). - -* Made sign specifiers work with ``__int128_t`` - (`#2773 `_). - -* Improved support for systems such as CHERI with extra data stored in pointers - (`#2932 `_). - Thanks `@davidchisnall (David Chisnall) `_. - -* Improved documentation - (`#2706 `_, - `#2712 `_, - `#2789 `_, - `#2803 `_, - `#2805 `_, - `#2815 `_, - `#2924 `_). - Thanks `@BRevzin (Barry Revzin) `_, - `@Pokechu22 `_, - `@setoye (Alta) `_, - `@rtobar `_, - `@rbrugo (Riccardo Brugo) `_, - `@anoonD (cre) `_, - `@leha-bot (Alex) `_. - -* Improved build configuration - (`#2766 `_, - `#2772 `_, - `#2836 `_, - `#2852 `_, - `#2907 `_, - `#2913 `_, - `#2914 `_). - Thanks `@kambala-decapitator (Andrey Filipenkov) - `_, - `@mattiasljungstrom (Mattias Ljungström) - `_, - `@kieselnb (Nick Kiesel) `_, - `@nathannaveen `_, - `@Vertexwahn `_. - -* Fixed various warnings and compilation issues - (`#2408 `_, - `#2507 `_, - `#2697 `_, - `#2715 `_, - `#2717 `_, - `#2722 `_, - `#2724 `_, - `#2725 `_, - `#2726 `_, - `#2728 `_, - `#2732 `_, - `#2738 `_, - `#2742 `_, - `#2744 `_, - `#2745 `_, - `#2746 `_, - `#2754 `_, - `#2755 `_, - `#2757 `_, - `#2758 `_, - `#2761 `_, - `#2762 `_, - `#2763 `_, - `#2765 `_, - `#2769 `_, - `#2770 `_, - `#2771 `_, - `#2777 `_, - `#2779 `_, - `#2782 `_, - `#2783 `_, - `#2794 `_, - `#2796 `_, - `#2797 `_, - `#2801 `_, - `#2802 `_, - `#2808 `_, - `#2818 `_, - `#2819 `_, - `#2829 `_, - `#2835 `_, - `#2848 `_, - `#2860 `_, - `#2861 `_, - `#2882 `_, - `#2886 `_, - `#2891 `_, - `#2892 `_, - `#2895 `_, - `#2896 `_, - `#2903 `_, - `#2906 `_, - `#2908 `_, - `#2909 `_, - `#2920 `_, - `#2922 `_, - `#2927 `_, - `#2929 `_, - `#2936 `_, - `#2937 `_, - `#2938 `_, - `#2951 `_, - `#2954 `_, - `#2957 `_, - `#2958 `_, - `#2960 `_). - Thanks `@matrackif `_ - `@Tobi823 (Tobias Hellmann) `_, - `@ivan-volnov (Ivan Volnov) `_, - `@VasiliPupkin256 `_, - `@federico-busato (Federico) `_, - `@barcharcraz (Charlie Barto) `_, - `@jk-jeon (Junekey Jeon) `_, - `@HazardyKnusperkeks (Björn Schäpers) - `_, - `@dalboris (Boris Dalstein) `_, - `@seanm (Sean McBride) `_, - `@gsjaardema (Greg Sjaardema) `_, - `@timsong-cpp `_, - `@seanm (Sean McBride) `_, - `@frithrah `_, - `@chronoxor (Ivan Shynkarenka) `_, - `@Agga `_, - `@madmaxoft (Mattes D) `_, - `@JurajX (Juraj) `_, - `@phprus (Vladislav Shchapov) `_, - `@Dani-Hub (Daniel Krügler) `_. - -8.1.1 - 2022-01-06 ------------------- - -* Restored ABI compatibility with version 8.0.x - (`#2695 `_, - `#2696 `_). - Thanks `@saraedum (Julian Rüth) `_. - -* Fixed chrono formatting on big endian systems - (`#2698 `_, - `#2699 `_). - Thanks `@phprus (Vladislav Shchapov) `_ and - `@xvitaly (Vitaly Zaitsev) `_. - -* Fixed a linkage error with mingw - (`#2691 `_, - `#2692 `_). - Thanks `@rbberger (Richard Berger) `_. - -8.1.0 - 2022-01-02 ------------------- - -* Optimized chrono formatting - (`#2500 `_, - `#2537 `_, - `#2541 `_, - `#2544 `_, - `#2550 `_, - `#2551 `_, - `#2576 `_, - `#2577 `_, - `#2586 `_, - `#2591 `_, - `#2594 `_, - `#2602 `_, - `#2617 `_, - `#2628 `_, - `#2633 `_, - `#2670 `_, - `#2671 `_). - - Processing of some specifiers such as ``%z`` and ``%Y`` is now up to 10-20 - times faster, for example on GCC 11 with libstdc++:: - - ---------------------------------------------------------------------------- - Benchmark Before After - ---------------------------------------------------------------------------- - FMTFormatter_z 261 ns 26.3 ns - FMTFormatterCompile_z 246 ns 11.6 ns - FMTFormatter_Y 263 ns 26.1 ns - FMTFormatterCompile_Y 244 ns 10.5 ns - ---------------------------------------------------------------------------- - - Thanks `@phprus (Vladislav Shchapov) `_ and - `@toughengineer (Pavel Novikov) `_. - -* Implemented subsecond formatting for chrono durations - (`#2623 `_). - For example (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - fmt::print("{:%S}", std::chrono::milliseconds(1234)); - } - - prints "01.234". - - Thanks `@matrackif `_. - -* Fixed handling of precision 0 when formatting chrono durations - (`#2587 `_, - `#2588 `_). - Thanks `@lukester1975 `_. - -* Fixed an overflow on invalid inputs in the ``tm`` formatter - (`#2564 `_). - Thanks `@phprus (Vladislav Shchapov) `_. - -* Added ``fmt::group_digits`` that formats integers with a non-localized digit - separator (comma) for groups of three digits. - For example (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - fmt::print("{} dollars", fmt::group_digits(1000000)); - } - - prints "1,000,000 dollars". - -* Added support for faint, conceal, reverse and blink text styles - (`#2394 `_): - - https://user-images.githubusercontent.com/576385/147710227-c68f5317-f8fa-42c3-9123-7c4ba3c398cb.mp4 - - Thanks `@benit8 (Benoît Lormeau) `_ and - `@data-man (Dmitry Atamanov) `_. - -* Added experimental support for compile-time floating point formatting - (`#2426 `_, - `#2470 `_). - It is currently limited to the header-only mode. - Thanks `@alexezeder (Alexey Ochapov) `_. - -* Added UDL-based named argument support to compile-time format string checks - (`#2640 `_, - `#2649 `_). - For example (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - using namespace fmt::literals; - fmt::print("{answer:s}", "answer"_a=42); - } - - gives a compile-time error on compilers with C++20 ``consteval`` and non-type - template parameter support (gcc 10+) because ``s`` is not a valid format - specifier for an integer. - - Thanks `@alexezeder (Alexey Ochapov) `_. - -* Implemented escaping of string range elements. - For example (`godbolt `__): - - .. code:: c++ - - #include - #include - - int main() { - fmt::print("{}", std::vector{"\naan"}); - } - - is now printed as:: - - ["\naan"] - - instead of:: - - [" - aan"] - -* Added an experimental ``?`` specifier for escaping strings. - (`#2674 `_). - Thanks `@BRevzin (Barry Revzin) `_. - -* Switched to JSON-like representation of maps and sets for consistency with - Python's ``str.format``. - For example (`godbolt `__): - - .. code:: c++ - - #include - #include - - int main() { - fmt::print("{}", std::map{{"answer", 42}}); - } - - is now printed as:: - - {"answer": 42} - -* Extended ``fmt::join`` to support C++20-only ranges - (`#2549 `_). - Thanks `@BRevzin (Barry Revzin) `_. - -* Optimized handling of non-const-iterable ranges and implemented initial - support for non-const-formattable types. - -* Disabled implicit conversions of scoped enums to integers that was - accidentally introduced in earlier versions - (`#1841 `_). - -* Deprecated implicit conversion of ``[const] signed char*`` and - ``[const] unsigned char*`` to C strings. - -* Deprecated ``_format``, a legacy UDL-based format API - (`#2646 `_). - Thanks `@alexezeder (Alexey Ochapov) `_. - -* Marked ``format``, ``formatted_size`` and ``to_string`` as ``[[nodiscard]]`` - (`#2612 `_). - `@0x8000-0000 (Florin Iucha) `_. - -* Added missing diagnostic when trying to format function and member pointers - as well as objects convertible to pointers which is explicitly disallowed - (`#2598 `_, - `#2609 `_, - `#2610 `_). - Thanks `@AlexGuteniev (Alex Guteniev) `_. - -* Optimized writing to a contiguous buffer with ``format_to_n`` - (`#2489 `_). - Thanks `@Roman-Koshelev `_. - -* Optimized writing to non-``char`` buffers - (`#2477 `_). - Thanks `@Roman-Koshelev `_. - -* Decimal point is now localized when using the ``L`` specifier. - -* Improved floating point formatter implementation - (`#2498 `_, - `#2499 `_). - Thanks `@Roman-Koshelev `_. - -* Fixed handling of very large precision in fixed format - (`#2616 `_). - -* Made a table of cached powers used in FP formatting static - (`#2509 `_). - Thanks `@jk-jeon (Junekey Jeon) `_. - -* Resolved a lookup ambiguity with C++20 format-related functions due to ADL - (`#2639 `_, - `#2641 `_). - Thanks `@mkurdej (Marek Kurdej) `_. - -* Removed unnecessary inline namespace qualification - (`#2642 `_, - `#2643 `_). - Thanks `@mkurdej (Marek Kurdej) `_. - -* Implemented argument forwarding in ``format_to_n`` - (`#2462 `_, - `#2463 `_). - Thanks `@owent (WenTao Ou) `_. - -* Fixed handling of implicit conversions in ``fmt::to_string`` and format string - compilation (`#2565 `_). - -* Changed the default access mode of files created by ``fmt::output_file`` to - ``-rw-r--r--`` for consistency with ``fopen`` - (`#2530 `_). - -* Make ``fmt::ostream::flush`` public - (`#2435 `_). - -* Improved C++14/17 attribute detection - (`#2615 `_). - Thanks `@AlexGuteniev (Alex Guteniev) `_. - -* Improved ``consteval`` detection for MSVC - (`#2559 `_). - Thanks `@DanielaE (Daniela Engert) `_. - -* Improved documentation - (`#2406 `_, - `#2446 `_, - `#2493 `_, - `#2513 `_, - `#2515 `_, - `#2522 `_, - `#2562 `_, - `#2575 `_, - `#2606 `_, - `#2620 `_, - `#2676 `_). - Thanks `@sobolevn (Nikita Sobolev) `_, - `@UnePierre (Max FERGER) `_, - `@zhsj `_, - `@phprus (Vladislav Shchapov) `_, - `@ericcurtin (Eric Curtin) `_, - `@Lounarok `_. - -* Improved fuzzers and added a fuzzer for chrono timepoint formatting - (`#2461 `_, - `#2469 `_). - `@pauldreik (Paul Dreik) `_, - -* Added the ``FMT_SYSTEM_HEADERS`` CMake option setting which marks {fmt}'s - headers as system. It can be used to suppress warnings - (`#2644 `_, - `#2651 `_). - Thanks `@alexezeder (Alexey Ochapov) `_. - -* Added the Bazel build system support - (`#2505 `_, - `#2516 `_). - Thanks `@Vertexwahn `_. - -* Improved build configuration and tests - (`#2437 `_, - `#2558 `_, - `#2648 `_, - `#2650 `_, - `#2663 `_, - `#2677 `_). - Thanks `@DanielaE (Daniela Engert) `_, - `@alexezeder (Alexey Ochapov) `_, - `@phprus (Vladislav Shchapov) `_. - -* Fixed various warnings and compilation issues - (`#2353 `_, - `#2356 `_, - `#2399 `_, - `#2408 `_, - `#2414 `_, - `#2427 `_, - `#2432 `_, - `#2442 `_, - `#2434 `_, - `#2439 `_, - `#2447 `_, - `#2450 `_, - `#2455 `_, - `#2465 `_, - `#2472 `_, - `#2474 `_, - `#2476 `_, - `#2478 `_, - `#2479 `_, - `#2481 `_, - `#2482 `_, - `#2483 `_, - `#2490 `_, - `#2491 `_, - `#2510 `_, - `#2518 `_, - `#2528 `_, - `#2529 `_, - `#2539 `_, - `#2540 `_, - `#2545 `_, - `#2555 `_, - `#2557 `_, - `#2570 `_, - `#2573 `_, - `#2582 `_, - `#2605 `_, - `#2611 `_, - `#2647 `_, - `#2627 `_, - `#2630 `_, - `#2635 `_, - `#2638 `_, - `#2653 `_, - `#2654 `_, - `#2661 `_, - `#2664 `_, - `#2684 `_). - Thanks `@DanielaE (Daniela Engert) `_, - `@mwinterb `_, - `@cdacamar (Cameron DaCamara) `_, - `@TrebledJ (Johnathan) `_, - `@bodomartin (brm) `_, - `@cquammen (Cory Quammen) `_, - `@white238 (Chris White) `_, - `@mmarkeloff (Max) `_, - `@palacaze (Pierre-Antoine Lacaze) `_, - `@jcelerier (Jean-Michaël Celerier) `_, - `@mborn-adi (Mathias Born) `_, - `@BrukerJWD (Jonathan W) `_, - `@spyridon97 (Spiros Tsalikis) `_, - `@phprus (Vladislav Shchapov) `_, - `@oliverlee (Oliver Lee) `_, - `@joshessman-llnl (Josh Essman) `_, - `@akohlmey (Axel Kohlmeyer) `_, - `@timkalu `_, - `@olupton (Olli Lupton) `_, - `@Acretock `_, - `@alexezeder (Alexey Ochapov) `_, - `@andrewcorrigan (Andrew Corrigan) `_, - `@lucpelletier `_, - `@HazardyKnusperkeks (Björn Schäpers) - `_. - -8.0.1 - 2021-07-02 ------------------- - -* Fixed the version number in the inline namespace - (`#2374 `_). - -* Added a missing presentation type check for ``std::string`` - (`#2402 `_). - -* Fixed a linkage error when mixing code built with clang and gcc - (`#2377 `_). - -* Fixed documentation issues - (`#2396 `_, - `#2403 `_, - `#2406 `_). - Thanks `@mkurdej (Marek Kurdej) `_. - -* Removed dead code in FP formatter ( - `#2398 `_). - Thanks `@javierhonduco (Javier Honduvilla Coto) - `_. - -* Fixed various warnings and compilation issues - (`#2351 `_, - `#2359 `_, - `#2365 `_, - `#2368 `_, - `#2370 `_, - `#2376 `_, - `#2381 `_, - `#2382 `_, - `#2386 `_, - `#2389 `_, - `#2395 `_, - `#2397 `_, - `#2400 `_, - `#2401 `_, - `#2407 `_). - Thanks `@zx2c4 (Jason A. Donenfeld) `_, - `@AidanSun05 (Aidan Sun) `_, - `@mattiasljungstrom (Mattias Ljungström) - `_, - `@joemmett (Jonathan Emmett) `_, - `@erengy (Eren Okka) `_, - `@patlkli (Patrick Geltinger) `_, - `@gsjaardema (Greg Sjaardema) `_, - `@phprus (Vladislav Shchapov) `_. - -8.0.0 - 2021-06-21 ------------------- - -* Enabled compile-time format string checks by default. - For example (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - fmt::print("{:d}", "I am not a number"); - } - - gives a compile-time error on compilers with C++20 ``consteval`` support - (gcc 10+, clang 11+) because ``d`` is not a valid format specifier for a - string. - - To pass a runtime string wrap it in ``fmt::runtime``: - - .. code:: c++ - - fmt::print(fmt::runtime("{:d}"), "I am not a number"); - -* Added compile-time formatting - (`#2019 `_, - `#2044 `_, - `#2056 `_, - `#2072 `_, - `#2075 `_, - `#2078 `_, - `#2129 `_, - `#2326 `_). - For example (`godbolt `__): - - .. code:: c++ - - #include - - consteval auto compile_time_itoa(int value) -> std::array { - auto result = std::array(); - fmt::format_to(result.data(), FMT_COMPILE("{}"), value); - return result; - } - - constexpr auto answer = compile_time_itoa(42); - - Most of the formatting functionality is available at compile time with a - notable exception of floating-point numbers and pointers. - Thanks `@alexezeder (Alexey Ochapov) `_. - -* Optimized handling of format specifiers during format string compilation. - For example, hexadecimal formatting (``"{:x}"``) is now 3-7x faster than - before when using ``format_to`` with format string compilation and a - stack-allocated buffer (`#1944 `_). - - Before (7.1.3):: - - ---------------------------------------------------------------------------- - Benchmark Time CPU Iterations - ---------------------------------------------------------------------------- - FMTCompileOld/0 15.5 ns 15.5 ns 43302898 - FMTCompileOld/42 16.6 ns 16.6 ns 43278267 - FMTCompileOld/273123 18.7 ns 18.6 ns 37035861 - FMTCompileOld/9223372036854775807 19.4 ns 19.4 ns 35243000 - ---------------------------------------------------------------------------- - - After (8.x):: - - ---------------------------------------------------------------------------- - Benchmark Time CPU Iterations - ---------------------------------------------------------------------------- - FMTCompileNew/0 1.99 ns 1.99 ns 360523686 - FMTCompileNew/42 2.33 ns 2.33 ns 279865664 - FMTCompileNew/273123 3.72 ns 3.71 ns 190230315 - FMTCompileNew/9223372036854775807 5.28 ns 5.26 ns 130711631 - ---------------------------------------------------------------------------- - - It is even faster than ``std::to_chars`` from libc++ compiled with clang on - macOS:: - - ---------------------------------------------------------------------------- - Benchmark Time CPU Iterations - ---------------------------------------------------------------------------- - ToChars/0 4.42 ns 4.41 ns 160196630 - ToChars/42 5.00 ns 4.98 ns 140735201 - ToChars/273123 7.26 ns 7.24 ns 95784130 - ToChars/9223372036854775807 8.77 ns 8.75 ns 75872534 - ---------------------------------------------------------------------------- - - In other cases, especially involving ``std::string`` construction, the - speed up is usually lower because handling format specifiers takes a smaller - fraction of the total time. - -* Added the ``_cf`` user-defined literal to represent a compiled format string. - It can be used instead of the ``FMT_COMPILE`` macro - (`#2043 `_, - `#2242 `_): - - .. code:: c++ - - #include - - using namespace fmt::literals; - auto s = fmt::format(FMT_COMPILE("{}"), 42); // 🙁 not modern - auto s = fmt::format("{}"_cf, 42); // 🙂 modern as hell - - It requires compiler support for class types in non-type template parameters - (a C++20 feature) which is available in GCC 9.3+. - Thanks `@alexezeder (Alexey Ochapov) `_. - -* Format string compilation now requires ``format`` functions of ``formatter`` - specializations for user-defined types to be ``const``: - - .. code:: c++ - - template <> struct fmt::formatter: formatter { - template - auto format(my_type obj, FormatContext& ctx) const { // Note const here. - // ... - } - }; - -* Added UDL-based named argument support to format string compilation - (`#2243 `_, - `#2281 `_). For example: - - .. code:: c++ - - #include - - using namespace fmt::literals; - auto s = fmt::format(FMT_COMPILE("{answer}"), "answer"_a = 42); - - Here the argument named "answer" is resolved at compile time with no - runtime overhead. - Thanks `@alexezeder (Alexey Ochapov) `_. - -* Added format string compilation support to ``fmt::print`` - (`#2280 `_, - `#2304 `_). - Thanks `@alexezeder (Alexey Ochapov) `_. - -* Added initial support for compiling {fmt} as a C++20 module - (`#2235 `_, - `#2240 `_, - `#2260 `_, - `#2282 `_, - `#2283 `_, - `#2288 `_, - `#2298 `_, - `#2306 `_, - `#2307 `_, - `#2309 `_, - `#2318 `_, - `#2324 `_, - `#2332 `_, - `#2340 `_). - Thanks `@DanielaE (Daniela Engert) `_. - -* Made symbols private by default reducing shared library size - (`#2301 `_). For example there was - a ~15% reported reduction on one platform. - Thanks `@sergiud (Sergiu Deitsch) `_. - -* Optimized includes making the result of preprocessing ``fmt/format.h`` - ~20% smaller with libstdc++/C++20 and slightly improving build times - (`#1998 `_). - -* Added support of ranges with non-const ``begin`` / ``end`` - (`#1953 `_). - Thanks `@kitegi (sarah) `_. - -* Added support of ``std::byte`` and other formattable types to ``fmt::join`` - (`#1981 `_, - `#2040 `_, - `#2050 `_, - `#2262 `_). For example: - - .. code:: c++ - - #include - #include - #include - - int main() { - auto bytes = std::vector{std::byte(4), std::byte(2)}; - fmt::print("{}", fmt::join(bytes, "")); - } - - prints "42". - - Thanks `@kamibo (Camille Bordignon) `_. - -* Implemented the default format for ``std::chrono::system_clock`` - (`#2319 `_, - `#2345 `_). For example: - - .. code:: c++ - - #include - - int main() { - fmt::print("{}", std::chrono::system_clock::now()); - } - - prints "2021-06-18 15:22:00" (the output depends on the current date and - time). Thanks `@sunmy2019 `_. - -* Made more chrono specifiers locale independent by default. Use the ``'L'`` - specifier to get localized formatting. For example: - - .. code:: c++ - - #include - - int main() { - std::locale::global(std::locale("ru_RU.UTF-8")); - auto monday = std::chrono::weekday(1); - fmt::print("{}\n", monday); // prints "Mon" - fmt::print("{:L}\n", monday); // prints "пн" - } - -* Improved locale handling in chrono formatting - (`#2337 `_, - `#2349 `_, - `#2350 `_). - Thanks `@phprus (Vladislav Shchapov) `_. - -* Deprecated ``fmt/locale.h`` moving the formatting functions that take a - locale to ``fmt/format.h`` (``char``) and ``fmt/xchar`` (other overloads). - This doesn't introduce a dependency on ```` so there is virtually no - compile time effect. - -* Deprecated an undocumented ``format_to`` overload that takes - ``basic_memory_buffer``. - -* Made parameter order in ``vformat_to`` consistent with ``format_to`` - (`#2327 `_). - -* Added support for time points with arbitrary durations - (`#2208 `_). For example: - - .. code:: c++ - - #include - - int main() { - using tp = std::chrono::time_point< - std::chrono::system_clock, std::chrono::seconds>; - fmt::print("{:%S}", tp(std::chrono::seconds(42))); - } - - prints "42". - -* Formatting floating-point numbers no longer produces trailing zeros by default - for consistency with ``std::format``. For example: - - .. code:: c++ - - #include - - int main() { - fmt::print("{0:.3}", 1.1); - } - - prints "1.1". Use the ``'#'`` specifier to keep trailing zeros. - -* Dropped a limit on the number of elements in a range and replaced ``{}`` with - ``[]`` as range delimiters for consistency with Python's ``str.format``. - -* The ``'L'`` specifier for locale-specific numeric formatting can now be - combined with presentation specifiers as in ``std::format``. For example: - - .. code:: c++ - - #include - #include - - int main() { - std::locale::global(std::locale("fr_FR.UTF-8")); - fmt::print("{0:.2Lf}", 0.42); - } - - prints "0,42". The deprecated ``'n'`` specifier has been removed. - -* Made the ``0`` specifier ignored for infinity and NaN - (`#2305 `_, - `#2310 `_). - Thanks `@Liedtke (Matthias Liedtke) `_. - -* Made the hexfloat formatting use the right alignment by default - (`#2308 `_, - `#2317 `_). - Thanks `@Liedtke (Matthias Liedtke) `_. - -* Removed the deprecated numeric alignment (``'='``). Use the ``'0'`` specifier - instead. - -* Removed the deprecated ``fmt/posix.h`` header that has been replaced with - ``fmt/os.h``. - -* Removed the deprecated ``format_to_n_context``, ``format_to_n_args`` and - ``make_format_to_n_args``. They have been replaced with ``format_context``, - ``format_args` and ``make_format_args`` respectively. - -* Moved ``wchar_t``-specific functions and types to ``fmt/xchar.h``. - You can define ``FMT_DEPRECATED_INCLUDE_XCHAR`` to automatically include - ``fmt/xchar.h`` from ``fmt/format.h`` but this will be disabled in the next - major release. - -* Fixed handling of the ``'+'`` specifier in localized formatting - (`#2133 `_). - -* Added support for the ``'s'`` format specifier that gives textual - representation of ``bool`` - (`#2094 `_, - `#2109 `_). For example: - - .. code:: c++ - - #include - - int main() { - fmt::print("{:s}", true); - } - - prints "true". - Thanks `@powercoderlol (Ivan Polyakov) `_. - -* Made ``fmt::ptr`` work with function pointers - (`#2131 `_). For example: - - .. code:: c++ - - #include - - int main() { - fmt::print("My main: {}\n", fmt::ptr(main)); - } - - Thanks `@mikecrowe (Mike Crowe) `_. - -* The undocumented support for specializing ``formatter`` for pointer types - has been removed. - -* Fixed ``fmt::formatted_size`` with format string compilation - (`#2141 `_, - `#2161 `_). - Thanks `@alexezeder (Alexey Ochapov) `_. - -* Fixed handling of empty format strings during format string compilation - (`#2042 `_): - - .. code:: c++ - - auto s = fmt::format(FMT_COMPILE("")); - - Thanks `@alexezeder (Alexey Ochapov) `_. - -* Fixed handling of enums in ``fmt::to_string`` - (`#2036 `_). - -* Improved width computation - (`#2033 `_, - `#2091 `_). For example: - - .. code:: c++ - - #include - - int main() { - fmt::print("{:-<10}{}\n", "你好", "世界"); - fmt::print("{:-<10}{}\n", "hello", "world"); - } - - prints - - .. image:: https://user-images.githubusercontent.com/576385/ - 119840373-cea3ca80-beb9-11eb-91e0-54266c48e181.png - - on a modern terminal. - -* The experimental fast output stream (``fmt::ostream``) is now truncated by - default for consistency with ``fopen`` - (`#2018 `_). For example: - - .. code:: c++ - - #include - - int main() { - fmt::ostream out1 = fmt::output_file("guide"); - out1.print("Zaphod"); - out1.close(); - fmt::ostream out2 = fmt::output_file("guide"); - out2.print("Ford"); - } - - writes "Ford" to the file "guide". To preserve the old file content if any - pass ``fmt::file::WRONLY | fmt::file::CREATE`` flags to ``fmt::output_file``. - -* Fixed moving of ``fmt::ostream`` that holds buffered data - (`#2197 `_, - `#2198 `_). - Thanks `@vtta `_. - -* Replaced the ``fmt::system_error`` exception with a function of the same - name that constructs ``std::system_error`` - (`#2266 `_). - -* Replaced the ``fmt::windows_error`` exception with a function of the same - name that constructs ``std::system_error`` with the category returned by - ``fmt::system_category()`` - (`#2274 `_, - `#2275 `_). - The latter is similar to ``std::sytem_category`` but correctly handles UTF-8. - Thanks `@phprus (Vladislav Shchapov) `_. - -* Replaced ``fmt::error_code`` with ``std::error_code`` and made it formattable - (`#2269 `_, - `#2270 `_, - `#2273 `_). - Thanks `@phprus (Vladislav Shchapov) `_. - -* Added speech synthesis support - (`#2206 `_). - -* Made ``format_to`` work with a memory buffer that has a custom allocator - (`#2300 `_). - Thanks `@voxmea `_. - -* Added ``Allocator::max_size`` support to ``basic_memory_buffer``. - (`#1960 `_). - Thanks `@phprus (Vladislav Shchapov) `_. - -* Added wide string support to ``fmt::join`` - (`#2236 `_). - Thanks `@crbrz `_. - -* Made iterators passed to ``formatter`` specializations via a format context - satisfy C++20 ``std::output_iterator`` requirements - (`#2156 `_, - `#2158 `_, - `#2195 `_, - `#2204 `_). - Thanks `@randomnetcat (Jason Cobb) `_. - -* Optimized the ``printf`` implementation - (`#1982 `_, - `#1984 `_, - `#2016 `_, - `#2164 `_). - Thanks `@rimathia `_ and - `@moiwi `_. - -* Improved detection of ``constexpr`` ``char_traits`` - (`#2246 `_, - `#2257 `_). - Thanks `@phprus (Vladislav Shchapov) `_. - -* Fixed writing to ``stdout`` when it is redirected to ``NUL`` on Windows - (`#2080 `_). - -* Fixed exception propagation from iterators - (`#2097 `_). - -* Improved ``strftime`` error handling - (`#2238 `_, - `#2244 `_). - Thanks `@yumeyao `_. - -* Stopped using deprecated GCC UDL template extension. - -* Added ``fmt/args.h`` to the install target - (`#2096 `_). - -* Error messages are now passed to assert when exceptions are disabled - (`#2145 `_). - Thanks `@NobodyXu (Jiahao XU) `_. - -* Added the ``FMT_MASTER_PROJECT`` CMake option to control build and install - targets when {fmt} is included via ``add_subdirectory`` - (`#2098 `_, - `#2100 `_). - Thanks `@randomizedthinking `_. - -* Improved build configuration - (`#2026 `_, - `#2122 `_). - Thanks `@luncliff (Park DongHa) `_ and - `@ibaned (Dan Ibanez) `_. - -* Fixed various warnings and compilation issues - (`#1947 `_, - `#1959 `_, - `#1963 `_, - `#1965 `_, - `#1966 `_, - `#1974 `_, - `#1975 `_, - `#1990 `_, - `#2000 `_, - `#2001 `_, - `#2002 `_, - `#2004 `_, - `#2006 `_, - `#2009 `_, - `#2010 `_, - `#2038 `_, - `#2039 `_, - `#2047 `_, - `#2053 `_, - `#2059 `_, - `#2065 `_, - `#2067 `_, - `#2068 `_, - `#2073 `_, - `#2103 `_, - `#2105 `_, - `#2106 `_, - `#2107 `_, - `#2116 `_, - `#2117 `_, - `#2118 `_, - `#2119 `_, - `#2127 `_, - `#2128 `_, - `#2140 `_, - `#2142 `_, - `#2143 `_, - `#2144 `_, - `#2147 `_, - `#2148 `_, - `#2149 `_, - `#2152 `_, - `#2160 `_, - `#2170 `_, - `#2175 `_, - `#2176 `_, - `#2177 `_, - `#2178 `_, - `#2179 `_, - `#2180 `_, - `#2181 `_, - `#2183 `_, - `#2184 `_, - `#2185 `_, - `#2186 `_, - `#2187 `_, - `#2190 `_, - `#2192 `_, - `#2194 `_, - `#2205 `_, - `#2210 `_, - `#2211 `_, - `#2215 `_, - `#2216 `_, - `#2218 `_, - `#2220 `_, - `#2228 `_, - `#2229 `_, - `#2230 `_, - `#2233 `_, - `#2239 `_, - `#2248 `_, - `#2252 `_, - `#2253 `_, - `#2255 `_, - `#2261 `_, - `#2278 `_, - `#2284 `_, - `#2287 `_, - `#2289 `_, - `#2290 `_, - `#2293 `_, - `#2295 `_, - `#2296 `_, - `#2297 `_, - `#2311 `_, - `#2313 `_, - `#2315 `_, - `#2320 `_, - `#2321 `_, - `#2323 `_, - `#2328 `_, - `#2329 `_, - `#2333 `_, - `#2338 `_, - `#2341 `_). - Thanks `@darklukee `_, - `@fagg (Ashton Fagg) `_, - `@killerbot242 (Lieven de Cock) `_, - `@jgopel (Jonathan Gopel) `_, - `@yeswalrus (Walter Gray) `_, - `@Finkman `_, - `@HazardyKnusperkeks (Björn Schäpers) `_, - `@dkavolis (Daumantas Kavolis) `_, - `@concatime (Issam Maghni) `_, - `@chronoxor (Ivan Shynkarenka) `_, - `@summivox (Yin Zhong) `_, - `@yNeo `_, - `@Apache-HB (Elliot) `_, - `@alexezeder (Alexey Ochapov) `_, - `@toojays (John Steele Scott) `_, - `@Brainy0207 `_, - `@vadz (VZ) `_, - `@imsherlock (Ryan Sherlock) `_, - `@phprus (Vladislav Shchapov) `_, - `@white238 (Chris White) `_, - `@yafshar (Yaser Afshar) `_, - `@BillyDonahue (Billy Donahue) `_, - `@jstaahl `_, - `@denchat `_, - `@DanielaE (Daniela Engert) `_, - `@ilyakurdyukov (Ilya Kurdyukov) `_, - `@ilmai `_, - `@JessyDL (Jessy De Lannoit) `_, - `@sergiud (Sergiu Deitsch) `_, - `@mwinterb `_, - `@sven-herrmann `_, - `@jmelas (John Melas) `_, - `@twoixter (Jose Miguel Pérez) `_, - `@crbrz `_, - `@upsj (Tobias Ribizel) `_. - -* Improved documentation - (`#1986 `_, - `#2051 `_, - `#2057 `_, - `#2081 `_, - `#2084 `_, - `#2312 `_). - Thanks `@imba-tjd (谭九鼎) `_, - `@0x416c69 (AlιAѕѕaѕѕιN) `_, - `@mordante `_. - -* Continuous integration and test improvements - (`#1969 `_, - `#1991 `_, - `#2020 `_, - `#2110 `_, - `#2114 `_, - `#2196 `_, - `#2217 `_, - `#2247 `_, - `#2256 `_, - `#2336 `_, - `#2346 `_). - Thanks `@jgopel (Jonathan Gopel) `_, - `@alexezeder (Alexey Ochapov) `_ and - `@DanielaE (Daniela Engert) `_. - -7.1.3 - 2020-11-24 ------------------- - -* Fixed handling of buffer boundaries in ``format_to_n`` - (`#1996 `_, - `#2029 `_). - -* Fixed linkage errors when linking with a shared library - (`#2011 `_). - -* Reintroduced ostream support to range formatters - (`#2014 `_). - -* Worked around an issue with mixing std versions in gcc - (`#2017 `_). - -7.1.2 - 2020-11-04 ------------------- - -* Fixed floating point formatting with large precision - (`#1976 `_). - -7.1.1 - 2020-11-01 ------------------- - -* Fixed ABI compatibility with 7.0.x - (`#1961 `_). - -* Added the ``FMT_ARM_ABI_COMPATIBILITY`` macro to work around ABI - incompatibility between GCC and Clang on ARM - (`#1919 `_). - -* Worked around a SFINAE bug in GCC 8 - (`#1957 `_). - -* Fixed linkage errors when building with GCC's LTO - (`#1955 `_). - -* Fixed a compilation error when building without ``__builtin_clz`` or equivalent - (`#1968 `_). - Thanks `@tohammer (Tobias Hammer) `_. - -* Fixed a sign conversion warning - (`#1964 `_). - Thanks `@OptoCloud `_. - -7.1.0 - 2020-10-25 ------------------- - -* Switched from `Grisu3 - `_ - to `Dragonbox `_ for the default - floating-point formatting which gives the shortest decimal representation - with round-trip guarantee and correct rounding - (`#1882 `_, - `#1887 `_, - `#1894 `_). This makes {fmt} up to - 20-30x faster than common implementations of ``std::ostringstream`` and - ``sprintf`` on `dtoa-benchmark `_ - and faster than double-conversion and Ryū: - - .. image:: https://user-images.githubusercontent.com/576385/ - 95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png - - It is possible to get even better performance at the cost of larger binary - size by compiling with the ``FMT_USE_FULL_CACHE_DRAGONBOX`` macro set to 1. - - Thanks `@jk-jeon (Junekey Jeon) `_. - -* Added an experimental unsynchronized file output API which, together with - `format string compilation `_, - can give `5-9 times speed up compared to fprintf - `_ - on common platforms (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - auto f = fmt::output_file("guide"); - f.print("The answer is {}.", 42); - } - -* Added a formatter for ``std::chrono::time_point`` - (`#1819 `_, - `#1837 `_). For example - (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - auto now = std::chrono::system_clock::now(); - fmt::print("The time is {:%H:%M:%S}.\n", now); - } - - Thanks `@adamburgess (Adam Burgess) `_. - -* Added support for ranges with non-const ``begin``/``end`` to ``fmt::join`` - (`#1784 `_, - `#1786 `_). For example - (`godbolt `__): - - .. code:: c++ - - #include - #include - - int main() { - using std::literals::string_literals::operator""s; - auto strs = std::array{"a"s, "bb"s, "ccc"s}; - auto range = strs | ranges::views::filter( - [] (const std::string &x) { return x.size() != 2; } - ); - fmt::print("{}\n", fmt::join(range, "")); - } - - prints "accc". - - Thanks `@tonyelewis (Tony E Lewis) `_. - -* Added a ``memory_buffer::append`` overload that takes a range - (`#1806 `_). - Thanks `@BRevzin (Barry Revzin) `_. - -* Improved handling of single code units in ``FMT_COMPILE``. For example: - - .. code:: c++ - - #include - - char* f(char* buf) { - return fmt::format_to(buf, FMT_COMPILE("x{}"), 42); - } - - compiles to just (`godbolt `__): - - .. code:: asm - - _Z1fPc: - movb $120, (%rdi) - xorl %edx, %edx - cmpl $42, _ZN3fmt2v76detail10basic_dataIvE23zero_or_powers_of_10_32E+8(%rip) - movl $3, %eax - seta %dl - subl %edx, %eax - movzwl _ZN3fmt2v76detail10basic_dataIvE6digitsE+84(%rip), %edx - cltq - addq %rdi, %rax - movw %dx, -2(%rax) - ret - - Here a single ``mov`` instruction writes ``'x'`` (``$120``) to the output - buffer. - -* Added dynamic width support to format string compilation - (`#1809 `_). - -* Improved error reporting for unformattable types: now you'll get the type name - directly in the error message instead of the note: - - .. code:: c++ - - #include - - struct how_about_no {}; - - int main() { - fmt::print("{}", how_about_no()); - } - - Error (`godbolt `__): - - ``fmt/core.h:1438:3: error: static_assert failed due to requirement - 'fmt::v7::formattable()' "Cannot format an argument. - To make type T formattable provide a formatter specialization: - https://fmt.dev/latest/api.html#udt" - ...`` - -* Added the `make_args_checked `_ - function template that allows you to write formatting functions with - compile-time format string checks and avoid binary code bloat - (`godbolt `__): - - .. code:: c++ - - void vlog(const char* file, int line, fmt::string_view format, - fmt::format_args args) { - fmt::print("{}: {}: ", file, line); - fmt::vprint(format, args); - } - - template - void log(const char* file, int line, const S& format, Args&&... args) { - vlog(file, line, format, - fmt::make_args_checked(format, args...)); - } - - #define MY_LOG(format, ...) \ - log(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__) - - MY_LOG("invalid squishiness: {}", 42); - -* Replaced ``snprintf`` fallback with a faster internal IEEE 754 ``float`` and - ``double`` formatter for arbitrary precision. For example - (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - fmt::print("{:.500}\n", 4.9406564584124654E-324); - } - - prints - - ``4.9406564584124654417656879286822137236505980261432476442558568250067550727020875186529983636163599237979656469544571773092665671035593979639877479601078187812630071319031140452784581716784898210368871863605699873072305000638740915356498438731247339727316961514003171538539807412623856559117102665855668676818703956031062493194527159149245532930545654440112748012970999954193198940908041656332452475714786901472678015935523861155013480352649347201937902681071074917033322268447533357208324319360923829e-324``. - -* Made ``format_to_n`` and ``formatted_size`` part of the `core API - `__ - (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - char buffer[10]; - auto result = fmt::format_to_n(buffer, sizeof(buffer), "{}", 42); - } - -* Added ``fmt::format_to_n`` overload with format string compilation - (`#1764 `_, - `#1767 `_, - `#1869 `_). For example - (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - char buffer[8]; - fmt::format_to_n(buffer, sizeof(buffer), FMT_COMPILE("{}"), 42); - } - - Thanks `@Kurkin (Dmitry Kurkin) `_, - `@alexezeder (Alexey Ochapov) `_. - -* Added ``fmt::format_to`` overload that take ``text_style`` - (`#1593 `_, - `#1842 `_, - `#1843 `_). For example - (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - std::string out; - fmt::format_to(std::back_inserter(out), - fmt::emphasis::bold | fg(fmt::color::red), - "The answer is {}.", 42); - } - - Thanks `@Naios (Denis Blank) `_. - -* Made the ``'#'`` specifier emit trailing zeros in addition to the decimal - point (`#1797 `_). For example - (`godbolt `__): - - .. code:: c++ - - #include - - int main() { - fmt::print("{:#.2g}", 0.5); - } - - prints ``0.50``. - -* Changed the default floating point format to not include ``.0`` for - consistency with ``std::format`` and ``std::to_chars`` - (`#1893 `_, - `#1943 `_). It is possible to get - the decimal point and trailing zero with the ``#`` specifier. - -* Fixed an issue with floating-point formatting that could result in addition of - a non-significant trailing zero in rare cases e.g. ``1.00e-34`` instead of - ``1.0e-34`` (`#1873 `_, - `#1917 `_). - -* Made ``fmt::to_string`` fallback on ``ostream`` insertion operator if - the ``formatter`` specialization is not provided - (`#1815 `_, - `#1829 `_). - Thanks `@alexezeder (Alexey Ochapov) `_. - -* Added support for the append mode to the experimental file API and - improved ``fcntl.h`` detection. - (`#1847 `_, - `#1848 `_). - Thanks `@t-wiser `_. - -* Fixed handling of types that have both an implicit conversion operator and - an overloaded ``ostream`` insertion operator - (`#1766 `_). - -* Fixed a slicing issue in an internal iterator type - (`#1822 `_). - Thanks `@BRevzin (Barry Revzin) `_. - -* Fixed an issue in locale-specific integer formatting - (`#1927 `_). - -* Fixed handling of exotic code unit types - (`#1870 `_, - `#1932 `_). - -* Improved ``FMT_ALWAYS_INLINE`` - (`#1878 `_). - Thanks `@jk-jeon (Junekey Jeon) `_. - -* Removed dependency on ``windows.h`` - (`#1900 `_). - Thanks `@bernd5 (Bernd Baumanns) `_. - -* Optimized counting of decimal digits on MSVC - (`#1890 `_). - Thanks `@mwinterb `_. - -* Improved documentation - (`#1772 `_, - `#1775 `_, - `#1792 `_, - `#1838 `_, - `#1888 `_, - `#1918 `_, - `#1939 `_). - Thanks `@leolchat (Léonard Gérard) `_, - `@pepsiman (Malcolm Parsons) `_, - `@Klaim (Joël Lamotte) `_, - `@ravijanjam (Ravi J) `_, - `@francesco-st `_, - `@udnaan (Adnan) `_. - -* Added the ``FMT_REDUCE_INT_INSTANTIATIONS`` CMake option that reduces the - binary code size at the cost of some integer formatting performance. This can - be useful for extremely memory-constrained embedded systems - (`#1778 `_, - `#1781 `_). - Thanks `@kammce (Khalil Estell) `_. - -* Added the ``FMT_USE_INLINE_NAMESPACES`` macro to control usage of inline - namespaces (`#1945 `_). - Thanks `@darklukee `_. - -* Improved build configuration - (`#1760 `_, - `#1770 `_, - `#1779 `_, - `#1783 `_, - `#1823 `_). - Thanks `@dvetutnev (Dmitriy Vetutnev) `_, - `@xvitaly (Vitaly Zaitsev) `_, - `@tambry (Raul Tambre) `_, - `@medithe `_, - `@martinwuehrer (Martin Wührer) `_. - -* Fixed various warnings and compilation issues - (`#1790 `_, - `#1802 `_, - `#1808 `_, - `#1810 `_, - `#1811 `_, - `#1812 `_, - `#1814 `_, - `#1816 `_, - `#1817 `_, - `#1818 `_, - `#1825 `_, - `#1836 `_, - `#1855 `_, - `#1856 `_, - `#1860 `_, - `#1877 `_, - `#1879 `_, - `#1880 `_, - `#1896 `_, - `#1897 `_, - `#1898 `_, - `#1904 `_, - `#1908 `_, - `#1911 `_, - `#1912 `_, - `#1928 `_, - `#1929 `_, - `#1935 `_, - `#1937 `_, - `#1942 `_, - `#1949 `_). - Thanks `@TheQwertiest `_, - `@medithe `_, - `@martinwuehrer (Martin Wührer) `_, - `@n16h7hunt3r `_, - `@Othereum (Seokjin Lee) `_, - `@gsjaardema (Greg Sjaardema) `_, - `@AlexanderLanin (Alexander Lanin) `_, - `@gcerretani (Giovanni Cerretani) `_, - `@chronoxor (Ivan Shynkarenka) `_, - `@noizefloor (Jan Schwers) `_, - `@akohlmey (Axel Kohlmeyer) `_, - `@jk-jeon (Junekey Jeon) `_, - `@rimathia `_, - `@rglarix (Riccardo Ghetta (larix)) `_, - `@moiwi `_, - `@heckad (Kazantcev Andrey) `_, - `@MarcDirven `_. - `@BartSiwek (Bart Siwek) `_, - `@darklukee `_. - -7.0.3 - 2020-08-06 ------------------- - -* Worked around broken ``numeric_limits`` for 128-bit integers - (`#1787 `_). - -* Added error reporting on missing named arguments - (`#1796 `_). - -* Stopped using 128-bit integers with clang-cl - (`#1800 `_). - Thanks `@Kingcom `_. - -* Fixed issues in locale-specific integer formatting - (`#1782 `_, - `#1801 `_). - -7.0.2 - 2020-07-29 ------------------- - -* Worked around broken ``numeric_limits`` for 128-bit integers - (`#1725 `_). - -* Fixed compatibility with CMake 3.4 - (`#1779 `_). - -* Fixed handling of digit separators in locale-specific formatting - (`#1782 `_). - -7.0.1 - 2020-07-07 ------------------- - -* Updated the inline version namespace name. - -* Worked around a gcc bug in mangling of alias templates - (`#1753 `_). - -* Fixed a linkage error on Windows - (`#1757 `_). - Thanks `@Kurkin (Dmitry Kurkin) `_. - -* Fixed minor issues with the documentation. - -7.0.0 - 2020-07-05 ------------------- - -* Reduced the library size. For example, on macOS a stripped test binary - statically linked with {fmt} `shrank from ~368k to less than 100k - `_. - -* Added a simpler and more efficient `format string compilation API - `_: - - .. code:: c++ - - #include - - // Converts 42 into std::string using the most efficient method and no - // runtime format string processing. - std::string s = fmt::format(FMT_COMPILE("{}"), 42); - - The old ``fmt::compile`` API is now deprecated. - -* Optimized integer formatting: ``format_to`` with format string compilation - and a stack-allocated buffer is now `faster than to_chars on both - libc++ and libstdc++ - `_. - -* Optimized handling of small format strings. For example, - - .. code:: c++ - - fmt::format("Result: {}: ({},{},{},{})", str1, str2, str3, str4, str5) - - is now ~40% faster (`#1685 `_). - -* Applied extern templates to improve compile times when using the core API - and ``fmt/format.h`` (`#1452 `_). - For example, on macOS with clang the compile time of a test translation unit - dropped from 2.3s to 0.3s with ``-O2`` and from 0.6s to 0.3s with the default - settings (``-O0``). - - Before (``-O2``):: - - % time c++ -c test.cc -I include -std=c++17 -O2 - c++ -c test.cc -I include -std=c++17 -O2 2.22s user 0.08s system 99% cpu 2.311 total - - After (``-O2``):: - - % time c++ -c test.cc -I include -std=c++17 -O2 - c++ -c test.cc -I include -std=c++17 -O2 0.26s user 0.04s system 98% cpu 0.303 total - - Before (default):: - - % time c++ -c test.cc -I include -std=c++17 - c++ -c test.cc -I include -std=c++17 0.53s user 0.06s system 98% cpu 0.601 total - - After (default):: - - % time c++ -c test.cc -I include -std=c++17 - c++ -c test.cc -I include -std=c++17 0.24s user 0.06s system 98% cpu 0.301 total - - It is still recommended to use ``fmt/core.h`` instead of ``fmt/format.h`` but - the compile time difference is now smaller. Thanks - `@alex3d `_ for the suggestion. - -* Named arguments are now stored on stack (no dynamic memory allocations) and - the compiled code is more compact and efficient. For example - - .. code:: c++ - - #include - - int main() { - fmt::print("The answer is {answer}\n", fmt::arg("answer", 42)); - } - - compiles to just (`godbolt `__) - - .. code:: asm - - .LC0: - .string "answer" - .LC1: - .string "The answer is {answer}\n" - main: - sub rsp, 56 - mov edi, OFFSET FLAT:.LC1 - mov esi, 23 - movabs rdx, 4611686018427387905 - lea rax, [rsp+32] - lea rcx, [rsp+16] - mov QWORD PTR [rsp+8], 1 - mov QWORD PTR [rsp], rax - mov DWORD PTR [rsp+16], 42 - mov QWORD PTR [rsp+32], OFFSET FLAT:.LC0 - mov DWORD PTR [rsp+40], 0 - call fmt::v6::vprint(fmt::v6::basic_string_view, - fmt::v6::format_args) - xor eax, eax - add rsp, 56 - ret - - .L.str.1: - .asciz "answer" - -* Implemented compile-time checks for dynamic width and precision - (`#1614 `_): - - .. code:: c++ - - #include - - int main() { - fmt::print(FMT_STRING("{0:{1}}"), 42); - } - - now gives a compilation error because argument 1 doesn't exist:: - - In file included from test.cc:1: - include/fmt/format.h:2726:27: error: constexpr variable 'invalid_format' must be - initialized by a constant expression - FMT_CONSTEXPR_DECL bool invalid_format = - ^ - ... - include/fmt/core.h:569:26: note: in call to - '&checker(s, {}).context_->on_error(&"argument not found"[0])' - if (id >= num_args_) on_error("argument not found"); - ^ - -* Added sentinel support to ``fmt::join`` - (`#1689 `_) - - .. code:: c++ - - struct zstring_sentinel {}; - bool operator==(const char* p, zstring_sentinel) { return *p == '\0'; } - bool operator!=(const char* p, zstring_sentinel) { return *p != '\0'; } - - struct zstring { - const char* p; - const char* begin() const { return p; } - zstring_sentinel end() const { return {}; } - }; - - auto s = fmt::format("{}", fmt::join(zstring{"hello"}, "_")); - // s == "h_e_l_l_o" - - Thanks `@BRevzin (Barry Revzin) `_. - -* Added support for named arguments, ``clear`` and ``reserve`` to - ``dynamic_format_arg_store`` - (`#1655 `_, - `#1663 `_, - `#1674 `_, - `#1677 `_). - Thanks `@vsolontsov-ll (Vladimir Solontsov) - `_. - -* Added support for the ``'c'`` format specifier to integral types for - compatibility with ``std::format`` - (`#1652 `_). - -* Replaced the ``'n'`` format specifier with ``'L'`` for compatibility with - ``std::format`` (`#1624 `_). - The ``'n'`` specifier can be enabled via the ``FMT_DEPRECATED_N_SPECIFIER`` - macro. - -* The ``'='`` format specifier is now disabled by default for compatibility with - ``std::format``. It can be enabled via the ``FMT_DEPRECATED_NUMERIC_ALIGN`` - macro. - -* Removed the following deprecated APIs: - - * ``FMT_STRING_ALIAS`` and ``fmt`` macros - replaced by ``FMT_STRING`` - * ``fmt::basic_string_view::char_type`` - replaced by - ``fmt::basic_string_view::value_type`` - * ``convert_to_int`` - * ``format_arg_store::types`` - * ``*parse_context`` - replaced by ``*format_parse_context`` - * ``FMT_DEPRECATED_INCLUDE_OS`` - * ``FMT_DEPRECATED_PERCENT`` - incompatible with ``std::format`` - * ``*writer`` - replaced by compiled format API - -* Renamed the ``internal`` namespace to ``detail`` - (`#1538 `_). The former is still - provided as an alias if the ``FMT_USE_INTERNAL`` macro is defined. - -* Improved compatibility between ``fmt::printf`` with the standard specs - (`#1595 `_, - `#1682 `_, - `#1683 `_, - `#1687 `_, - `#1699 `_). - Thanks `@rimathia `_. - -* Fixed handling of ``operator<<`` overloads that use ``copyfmt`` - (`#1666 `_). - -* Added the ``FMT_OS`` CMake option to control inclusion of OS-specific APIs - in the fmt target. This can be useful for embedded platforms - (`#1654 `_, - `#1656 `_). - Thanks `@kwesolowski (Krzysztof Wesolowski) - `_. - -* Replaced ``FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION`` with the ``FMT_FUZZ`` - macro to prevent interferring with fuzzing of projects using {fmt} - (`#1650 `_). - Thanks `@asraa (Asra Ali) `_. - -* Fixed compatibility with emscripten - (`#1636 `_, - `#1637 `_). - Thanks `@ArthurSonzogni (Arthur Sonzogni) - `_. - -* Improved documentation - (`#704 `_, - `#1643 `_, - `#1660 `_, - `#1681 `_, - `#1691 `_, - `#1706 `_, - `#1714 `_, - `#1721 `_, - `#1739 `_, - `#1740 `_, - `#1741 `_, - `#1751 `_). - Thanks `@senior7515 (Alexander Gallego) `_, - `@lsr0 (Lindsay Roberts) `_, - `@puetzk (Kevin Puetz) `_, - `@fpelliccioni (Fernando Pelliccioni) `_, - Alexey Kuzmenko, `@jelly (jelle van der Waa) `_, - `@claremacrae (Clare Macrae) `_, - `@jiapengwen (文佳鹏) `_, - `@gsjaardema (Greg Sjaardema) `_, - `@alexey-milovidov `_. - -* Implemented various build configuration fixes and improvements - (`#1603 `_, - `#1657 `_, - `#1702 `_, - `#1728 `_). - Thanks `@scramsby (Scott Ramsby) `_, - `@jtojnar (Jan Tojnar) `_, - `@orivej (Orivej Desh) `_, - `@flagarde `_. - -* Fixed various warnings and compilation issues - (`#1616 `_, - `#1620 `_, - `#1622 `_, - `#1625 `_, - `#1627 `_, - `#1628 `_, - `#1629 `_, - `#1631 `_, - `#1633 `_, - `#1649 `_, - `#1658 `_, - `#1661 `_, - `#1667 `_, - `#1668 `_, - `#1669 `_, - `#1692 `_, - `#1696 `_, - `#1697 `_, - `#1707 `_, - `#1712 `_, - `#1716 `_, - `#1722 `_, - `#1724 `_, - `#1729 `_, - `#1738 `_, - `#1742 `_, - `#1743 `_, - `#1744 `_, - `#1747 `_, - `#1750 `_). - Thanks `@gsjaardema (Greg Sjaardema) `_, - `@gabime (Gabi Melman) `_, - `@johnor (Johan) `_, - `@Kurkin (Dmitry Kurkin) `_, - `@invexed (James Beach) `_, - `@peterbell10 `_, - `@daixtrose (Markus Werle) `_, - `@petrutlucian94 (Lucian Petrut) `_, - `@Neargye (Daniil Goncharov) `_, - `@ambitslix (Attila M. Szilagyi) `_, - `@gabime (Gabi Melman) `_, - `@erthink (Leonid Yuriev) `_, - `@tohammer (Tobias Hammer) `_, - `@0x8000-0000 (Florin Iucha) `_. - -6.2.1 - 2020-05-09 ------------------- - -* Fixed ostream support in ``sprintf`` - (`#1631 `_). - -* Fixed type detection when using implicit conversion to ``string_view`` and - ostream ``operator<<`` inconsistently - (`#1662 `_). - -6.2.0 - 2020-04-05 ------------------- - -* Improved error reporting when trying to format an object of a non-formattable - type: - - .. code:: c++ - - fmt::format("{}", S()); - - now gives:: - - include/fmt/core.h:1015:5: error: static_assert failed due to requirement - 'formattable' "Cannot format argument. To make type T formattable provide a - formatter specialization: - https://fmt.dev/latest/api.html#formatting-user-defined-types" - static_assert( - ^ - ... - note: in instantiation of function template specialization - 'fmt::v6::format' requested here - fmt::format("{}", S()); - ^ - - if ``S`` is not formattable. - -* Reduced the library size by ~10%. - -* Always print decimal point if ``#`` is specified - (`#1476 `_, - `#1498 `_): - - .. code:: c++ - - fmt::print("{:#.0f}", 42.0); - - now prints ``42.`` - -* Implemented the ``'L'`` specifier for locale-specific numeric formatting to - improve compatibility with ``std::format``. The ``'n'`` specifier is now - deprecated and will be removed in the next major release. - -* Moved OS-specific APIs such as ``windows_error`` from ``fmt/format.h`` to - ``fmt/os.h``. You can define ``FMT_DEPRECATED_INCLUDE_OS`` to automatically - include ``fmt/os.h`` from ``fmt/format.h`` for compatibility but this will be - disabled in the next major release. - -* Added precision overflow detection in floating-point formatting. - -* Implemented detection of invalid use of ``fmt::arg``. - -* Used ``type_identity`` to block unnecessary template argument deduction. - Thanks Tim Song. - -* Improved UTF-8 handling - (`#1109 `_): - - .. code:: c++ - - fmt::print("┌{0:─^{2}}┐\n" - "│{1: ^{2}}│\n" - "└{0:─^{2}}┘\n", "", "Привет, мир!", 20); - - now prints:: - - ┌────────────────────┐ - │ Привет, мир! │ - └────────────────────┘ - - on systems that support Unicode. - -* Added experimental dynamic argument storage - (`#1170 `_, - `#1584 `_): - - .. code:: c++ - - fmt::dynamic_format_arg_store store; - store.push_back("answer"); - store.push_back(42); - fmt::vprint("The {} is {}.\n", store); - - prints:: - - The answer is 42. - - Thanks `@vsolontsov-ll (Vladimir Solontsov) - `_. - -* Made ``fmt::join`` accept ``initializer_list`` - (`#1591 `_). - Thanks `@Rapotkinnik (Nikolay Rapotkin) `_. - -* Fixed handling of empty tuples - (`#1588 `_). - -* Fixed handling of output iterators in ``format_to_n`` - (`#1506 `_). - -* Fixed formatting of ``std::chrono::duration`` types to wide output - (`#1533 `_). - Thanks `@zeffy (pilao) `_. - -* Added const ``begin`` and ``end`` overload to buffers - (`#1553 `_). - Thanks `@dominicpoeschko `_. - -* Added the ability to disable floating-point formatting via ``FMT_USE_FLOAT``, - ``FMT_USE_DOUBLE`` and ``FMT_USE_LONG_DOUBLE`` macros for extremely - memory-constrained embedded system - (`#1590 `_). - Thanks `@albaguirre (Alberto Aguirre) `_. - -* Made ``FMT_STRING`` work with ``constexpr`` ``string_view`` - (`#1589 `_). - Thanks `@scramsby (Scott Ramsby) `_. - -* Implemented a minor optimization in the format string parser - (`#1560 `_). - Thanks `@IkarusDeveloper `_. - -* Improved attribute detection - (`#1469 `_, - `#1475 `_, - `#1576 `_). - Thanks `@federico-busato (Federico) `_, - `@chronoxor (Ivan Shynkarenka) `_, - `@refnum `_. - -* Improved documentation - (`#1481 `_, - `#1523 `_). - Thanks `@JackBoosY (Jack·Boos·Yu) `_, - `@imba-tjd (谭九鼎) `_. - -* Fixed symbol visibility on Linux when compiling with ``-fvisibility=hidden`` - (`#1535 `_). - Thanks `@milianw (Milian Wolff) `_. - -* Implemented various build configuration fixes and improvements - (`#1264 `_, - `#1460 `_, - `#1534 `_, - `#1536 `_, - `#1545 `_, - `#1546 `_, - `#1566 `_, - `#1582 `_, - `#1597 `_, - `#1598 `_). - Thanks `@ambitslix (Attila M. Szilagyi) `_, - `@jwillikers (Jordan Williams) `_, - `@stac47 (Laurent Stacul) `_. - -* Fixed various warnings and compilation issues - (`#1433 `_, - `#1461 `_, - `#1470 `_, - `#1480 `_, - `#1485 `_, - `#1492 `_, - `#1493 `_, - `#1504 `_, - `#1505 `_, - `#1512 `_, - `#1515 `_, - `#1516 `_, - `#1518 `_, - `#1519 `_, - `#1520 `_, - `#1521 `_, - `#1522 `_, - `#1524 `_, - `#1530 `_, - `#1531 `_, - `#1532 `_, - `#1539 `_, - `#1547 `_, - `#1548 `_, - `#1554 `_, - `#1567 `_, - `#1568 `_, - `#1569 `_, - `#1571 `_, - `#1573 `_, - `#1575 `_, - `#1581 `_, - `#1583 `_, - `#1586 `_, - `#1587 `_, - `#1594 `_, - `#1596 `_, - `#1604 `_, - `#1606 `_, - `#1607 `_, - `#1609 `_). - Thanks `@marti4d (Chris Martin) `_, - `@iPherian `_, - `@parkertomatoes `_, - `@gsjaardema (Greg Sjaardema) `_, - `@chronoxor (Ivan Shynkarenka) `_, - `@DanielaE (Daniela Engert) `_, - `@torsten48 `_, - `@tohammer (Tobias Hammer) `_, - `@lefticus (Jason Turner) `_, - `@ryusakki (Haise) `_, - `@adnsv (Alex Denisov) `_, - `@fghzxm `_, - `@refnum `_, - `@pramodk (Pramod Kumbhar) `_, - `@Spirrwell `_, - `@scramsby (Scott Ramsby) `_. - -6.1.2 - 2019-12-11 ------------------- - -* Fixed ABI compatibility with ``libfmt.so.6.0.0`` - (`#1471 `_). - -* Fixed handling types convertible to ``std::string_view`` - (`#1451 `_). - Thanks `@denizevrenci (Deniz Evrenci) `_. - -* Made CUDA test an opt-in enabled via the ``FMT_CUDA_TEST`` CMake option. - -* Fixed sign conversion warnings - (`#1440 `_). - Thanks `@0x8000-0000 (Florin Iucha) `_. - -6.1.1 - 2019-12-04 ------------------- - -* Fixed shared library build on Windows - (`#1443 `_, - `#1445 `_, - `#1446 `_, - `#1450 `_). - Thanks `@egorpugin (Egor Pugin) `_, - `@bbolli (Beat Bolli) `_. - -* Added a missing decimal point in exponent notation with trailing zeros. - -* Removed deprecated ``format_arg_store::TYPES``. - -6.1.0 - 2019-12-01 ------------------- - -* {fmt} now formats IEEE 754 ``float`` and ``double`` using the shortest decimal - representation with correct rounding by default: - - .. code:: c++ - - #include - #include - - int main() { - fmt::print("{}", M_PI); - } - - prints ``3.141592653589793``. - -* Made the fast binary to decimal floating-point formatter the default, - simplified it and improved performance. {fmt} is now 15 times faster than - libc++'s ``std::ostringstream``, 11 times faster than ``printf`` and 10% - faster than double-conversion on `dtoa-benchmark - `_: - - ================== ========= ======= - Function Time (ns) Speedup - ================== ========= ======= - ostringstream 1,346.30 1.00x - ostrstream 1,195.74 1.13x - sprintf 995.08 1.35x - doubleconv 99.10 13.59x - fmt 88.34 15.24x - ================== ========= ======= - - .. image:: https://user-images.githubusercontent.com/576385/ - 69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png - -* {fmt} no longer converts ``float`` arguments to ``double``. In particular this - improves the default (shortest) representation of floats and makes - ``fmt::format`` consistent with ``std::format`` specs - (`#1336 `_, - `#1353 `_, - `#1360 `_, - `#1361 `_): - - .. code:: c++ - - fmt::print("{}", 0.1f); - - prints ``0.1`` instead of ``0.10000000149011612``. - - Thanks `@orivej (Orivej Desh) `_. - -* Made floating-point formatting output consistent with ``printf``/iostreams - (`#1376 `_, - `#1417 `_). - -* Added support for 128-bit integers - (`#1287 `_): - - .. code:: c++ - - fmt::print("{}", std::numeric_limits<__int128_t>::max()); - - prints ``170141183460469231731687303715884105727``. - - Thanks `@denizevrenci (Deniz Evrenci) `_. - -* The overload of ``print`` that takes ``text_style`` is now atomic, i.e. the - output from different threads doesn't interleave - (`#1351 `_). - Thanks `@tankiJong (Tanki Zhang) `_. - -* Made compile time in the header-only mode ~20% faster by reducing the number - of template instantiations. ``wchar_t`` overload of ``vprint`` was moved from - ``fmt/core.h`` to ``fmt/format.h``. - -* Added an overload of ``fmt::join`` that works with tuples - (`#1322 `_, - `#1330 `_): - - .. code:: c++ - - #include - #include - - int main() { - std::tuple t{'a', 1, 2.0f}; - fmt::print("{}", t); - } - - prints ``('a', 1, 2.0)``. - - Thanks `@jeremyong (Jeremy Ong) `_. - -* Changed formatting of octal zero with prefix from "00" to "0": - - .. code:: c++ - - fmt::print("{:#o}", 0); - - prints ``0``. - -* The locale is now passed to ostream insertion (``<<``) operators - (`#1406 `_): - - .. code:: c++ - - #include - #include - - struct S { - double value; - }; - - std::ostream& operator<<(std::ostream& os, S s) { - return os << s.value; - } - - int main() { - auto s = fmt::format(std::locale("fr_FR.UTF-8"), "{}", S{0.42}); - // s == "0,42" - } - - Thanks `@dlaugt (Daniel Laügt) `_. - -* Locale-specific number formatting now uses grouping - (`#1393 `_ - `#1394 `_). - Thanks `@skrdaniel `_. - -* Fixed handling of types with deleted implicit rvalue conversion to - ``const char**`` (`#1421 `_): - - .. code:: c++ - - struct mystring { - operator const char*() const&; - operator const char*() &; - operator const char*() const&& = delete; - operator const char*() && = delete; - }; - mystring str; - fmt::print("{}", str); // now compiles - -* Enums are now mapped to correct underlying types instead of ``int`` - (`#1286 `_). - Thanks `@agmt (Egor Seredin) `_. - -* Enum classes are no longer implicitly converted to ``int`` - (`#1424 `_). - -* Added ``basic_format_parse_context`` for consistency with C++20 - ``std::format`` and deprecated ``basic_parse_context``. - -* Fixed handling of UTF-8 in precision - (`#1389 `_, - `#1390 `_). - Thanks `@tajtiattila (Attila Tajti) `_. - -* {fmt} can now be installed on Linux, macOS and Windows with - `Conda `__ using its - `conda-forge `__ - `package `__ - (`#1410 `_):: - - conda install -c conda-forge fmt - - Thanks `@tdegeus (Tom de Geus) `_. - -* Added a CUDA test (`#1285 `_, - `#1317 `_). - Thanks `@luncliff (Park DongHa) `_ and - `@risa2000 `_. - -* Improved documentation (`#1276 `_, - `#1291 `_, - `#1296 `_, - `#1315 `_, - `#1332 `_, - `#1337 `_, - `#1395 `_ - `#1418 `_). - Thanks - `@waywardmonkeys (Bruce Mitchener) `_, - `@pauldreik (Paul Dreik) `_, - `@jackoalan (Jack Andersen) `_. - -* Various code improvements - (`#1358 `_, - `#1407 `_). - Thanks `@orivej (Orivej Desh) `_, - `@dpacbach (David P. Sicilia) `_, - -* Fixed compile-time format string checks for user-defined types - (`#1292 `_). - -* Worked around a false positive in ``unsigned-integer-overflow`` sanitizer - (`#1377 `_). - -* Fixed various warnings and compilation issues - (`#1273 `_, - `#1278 `_, - `#1280 `_, - `#1281 `_, - `#1288 `_, - `#1290 `_, - `#1301 `_, - `#1305 `_, - `#1306 `_, - `#1309 `_, - `#1312 `_, - `#1313 `_, - `#1316 `_, - `#1319 `_, - `#1320 `_, - `#1326 `_, - `#1328 `_, - `#1344 `_, - `#1345 `_, - `#1347 `_, - `#1349 `_, - `#1354 `_, - `#1362 `_, - `#1366 `_, - `#1364 `_, - `#1370 `_, - `#1371 `_, - `#1385 `_, - `#1388 `_, - `#1397 `_, - `#1414 `_, - `#1416 `_, - `#1422 `_ - `#1427 `_, - `#1431 `_, - `#1433 `_). - Thanks `@hhb `_, - `@gsjaardema (Greg Sjaardema) `_, - `@gabime (Gabi Melman) `_, - `@neheb (Rosen Penev) `_, - `@vedranmiletic (Vedran Miletić) `_, - `@dkavolis (Daumantas Kavolis) `_, - `@mwinterb `_, - `@orivej (Orivej Desh) `_, - `@denizevrenci (Deniz Evrenci) `_ - `@leonklingele `_, - `@chronoxor (Ivan Shynkarenka) `_, - `@kent-tri `_, - `@0x8000-0000 (Florin Iucha) `_, - `@marti4d (Chris Martin) `_. - -6.0.0 - 2019-08-26 ------------------- - -* Switched to the `MIT license - `_ - with an optional exception that allows distributing binary code without - attribution. - -* Floating-point formatting is now locale-independent by default: - - .. code:: c++ - - #include - #include - - int main() { - std::locale::global(std::locale("ru_RU.UTF-8")); - fmt::print("value = {}", 4.2); - } - - prints "value = 4.2" regardless of the locale. - - For locale-specific formatting use the ``n`` specifier: - - .. code:: c++ - - std::locale::global(std::locale("ru_RU.UTF-8")); - fmt::print("value = {:n}", 4.2); - - prints "value = 4,2". - -* Added an experimental Grisu floating-point formatting algorithm - implementation (disabled by default). To enable it compile with the - ``FMT_USE_GRISU`` macro defined to 1: - - .. code:: c++ - - #define FMT_USE_GRISU 1 - #include - - auto s = fmt::format("{}", 4.2); // formats 4.2 using Grisu - - With Grisu enabled, {fmt} is 13x faster than ``std::ostringstream`` (libc++) - and 10x faster than ``sprintf`` on `dtoa-benchmark - `_ (`full results - `_): - - .. image:: https://user-images.githubusercontent.com/576385/ - 54883977-9fe8c000-4e28-11e9-8bde-272d122e7c52.jpg - -* Separated formatting and parsing contexts for consistency with - `C++20 std::format `_, removing the - undocumented ``basic_format_context::parse_context()`` function. - -* Added `oss-fuzz `_ support - (`#1199 `_). - Thanks `@pauldreik (Paul Dreik) `_. - -* ``formatter`` specializations now always take precedence over ``operator<<`` - (`#952 `_): - - .. code:: c++ - - #include - #include - - struct S {}; - - std::ostream& operator<<(std::ostream& os, S) { - return os << 1; - } - - template <> - struct fmt::formatter : fmt::formatter { - auto format(S, format_context& ctx) { - return formatter::format(2, ctx); - } - }; - - int main() { - std::cout << S() << "\n"; // prints 1 using operator<< - fmt::print("{}\n", S()); // prints 2 using formatter - } - -* Introduced the experimental ``fmt::compile`` function that does format string - compilation (`#618 `_, - `#1169 `_, - `#1171 `_): - - .. code:: c++ - - #include - - auto f = fmt::compile("{}"); - std::string s = fmt::format(f, 42); // can be called multiple times to - // format different values - // s == "42" - - It moves the cost of parsing a format string outside of the format function - which can be beneficial when identically formatting many objects of the same - types. Thanks `@stryku (Mateusz Janek) `_. - -* Added experimental ``%`` format specifier that formats floating-point values - as percentages (`#1060 `_, - `#1069 `_, - `#1071 `_): - - .. code:: c++ - - auto s = fmt::format("{:.1%}", 0.42); // s == "42.0%" - - Thanks `@gawain-bolton (Gawain Bolton) `_. - -* Implemented precision for floating-point durations - (`#1004 `_, - `#1012 `_): - - .. code:: c++ - - auto s = fmt::format("{:.1}", std::chrono::duration(1.234)); - // s == 1.2s - - Thanks `@DanielaE (Daniela Engert) `_. - -* Implemented ``chrono`` format specifiers ``%Q`` and ``%q`` that give the value - and the unit respectively (`#1019 `_): - - .. code:: c++ - - auto value = fmt::format("{:%Q}", 42s); // value == "42" - auto unit = fmt::format("{:%q}", 42s); // unit == "s" - - Thanks `@DanielaE (Daniela Engert) `_. - -* Fixed handling of dynamic width in chrono formatter: - - .. code:: c++ - - auto s = fmt::format("{0:{1}%H:%M:%S}", std::chrono::seconds(12345), 12); - // ^ width argument index ^ width - // s == "03:25:45 " - - Thanks Howard Hinnant. - -* Removed deprecated ``fmt/time.h``. Use ``fmt/chrono.h`` instead. - -* Added ``fmt::format`` and ``fmt::vformat`` overloads that take ``text_style`` - (`#993 `_, - `#994 `_): - - .. code:: c++ - - #include - - std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), - "The answer is {}.", 42); - - Thanks `@Naios (Denis Blank) `_. - -* Removed the deprecated color API (``print_colored``). Use the new API, namely - ``print`` overloads that take ``text_style`` instead. - -* Made ``std::unique_ptr`` and ``std::shared_ptr`` formattable as pointers via - ``fmt::ptr`` (`#1121 `_): - - .. code:: c++ - - std::unique_ptr p = ...; - fmt::print("{}", fmt::ptr(p)); // prints p as a pointer - - Thanks `@sighingnow (Tao He) `_. - -* Made ``print`` and ``vprint`` report I/O errors - (`#1098 `_, - `#1099 `_). - Thanks `@BillyDonahue (Billy Donahue) `_. - -* Marked deprecated APIs with the ``[[deprecated]]`` attribute and removed - internal uses of deprecated APIs - (`#1022 `_). - Thanks `@eliaskosunen (Elias Kosunen) `_. - -* Modernized the codebase using more C++11 features and removing workarounds. - Most importantly, ``buffer_context`` is now an alias template, so - use ``buffer_context`` instead of ``buffer_context::type``. - These features require GCC 4.8 or later. - -* ``formatter`` specializations now always take precedence over implicit - conversions to ``int`` and the undocumented ``convert_to_int`` trait - is now deprecated. - -* Moved the undocumented ``basic_writer``, ``writer``, and ``wwriter`` types - to the ``internal`` namespace. - -* Removed deprecated ``basic_format_context::begin()``. Use ``out()`` instead. - -* Disallowed passing the result of ``join`` as an lvalue to prevent misuse. - -* Refactored the undocumented structs that represent parsed format specifiers - to simplify the API and allow multibyte fill. - -* Moved SFINAE to template parameters to reduce symbol sizes. - -* Switched to ``fputws`` for writing wide strings so that it's no longer - required to call ``_setmode`` on Windows - (`#1229 `_, - `#1243 `_). - Thanks `@jackoalan (Jack Andersen) `_. - -* Improved literal-based API - (`#1254 `_). - Thanks `@sylveon (Charles Milette) `_. - -* Added support for exotic platforms without ``uintptr_t`` such as IBM i - (AS/400) which has 128-bit pointers and only 64-bit integers - (`#1059 `_). - -* Added `Sublime Text syntax highlighting config - `_ - (`#1037 `_). - Thanks `@Kronuz (Germán Méndez Bravo) `_. - -* Added the ``FMT_ENFORCE_COMPILE_STRING`` macro to enforce the use of - compile-time format strings - (`#1231 `_). - Thanks `@jackoalan (Jack Andersen) `_. - -* Stopped setting ``CMAKE_BUILD_TYPE`` if {fmt} is a subproject - (`#1081 `_). - -* Various build improvements - (`#1039 `_, - `#1078 `_, - `#1091 `_, - `#1103 `_, - `#1177 `_). - Thanks `@luncliff (Park DongHa) `_, - `@jasonszang (Jason Shuo Zang) `_, - `@olafhering (Olaf Hering) `_, - `@Lecetem `_, - `@pauldreik (Paul Dreik) `_. - -* Improved documentation - (`#1049 `_, - `#1051 `_, - `#1083 `_, - `#1113 `_, - `#1114 `_, - `#1146 `_, - `#1180 `_, - `#1250 `_, - `#1252 `_, - `#1265 `_). - Thanks `@mikelui (Michael Lui) `_, - `@foonathan (Jonathan Müller) `_, - `@BillyDonahue (Billy Donahue) `_, - `@jwakely (Jonathan Wakely) `_, - `@kaisbe (Kais Ben Salah) `_, - `@sdebionne (Samuel Debionne) `_. - -* Fixed ambiguous formatter specialization in ``fmt/ranges.h`` - (`#1123 `_). - -* Fixed formatting of a non-empty ``std::filesystem::path`` which is an - infinitely deep range of its components - (`#1268 `_). - -* Fixed handling of general output iterators when formatting characters - (`#1056 `_, - `#1058 `_). - Thanks `@abolz (Alexander Bolz) `_. - -* Fixed handling of output iterators in ``formatter`` specialization for - ranges (`#1064 `_). - -* Fixed handling of exotic character types - (`#1188 `_). - -* Made chrono formatting work with exceptions disabled - (`#1062 `_). - -* Fixed DLL visibility issues - (`#1134 `_, - `#1147 `_). - Thanks `@denchat `_. - -* Disabled the use of UDL template extension on GCC 9 - (`#1148 `_). - -* Removed misplaced ``format`` compile-time checks from ``printf`` - (`#1173 `_). - -* Fixed issues in the experimental floating-point formatter - (`#1072 `_, - `#1129 `_, - `#1153 `_, - `#1155 `_, - `#1210 `_, - `#1222 `_). - Thanks `@alabuzhev (Alex Alabuzhev) `_. - -* Fixed bugs discovered by fuzzing or during fuzzing integration - (`#1124 `_, - `#1127 `_, - `#1132 `_, - `#1135 `_, - `#1136 `_, - `#1141 `_, - `#1142 `_, - `#1178 `_, - `#1179 `_, - `#1194 `_). - Thanks `@pauldreik (Paul Dreik) `_. - -* Fixed building tests on FreeBSD and Hurd - (`#1043 `_). - Thanks `@jackyf (Eugene V. Lyubimkin) `_. - -* Fixed various warnings and compilation issues - (`#998 `_, - `#1006 `_, - `#1008 `_, - `#1011 `_, - `#1025 `_, - `#1027 `_, - `#1028 `_, - `#1029 `_, - `#1030 `_, - `#1031 `_, - `#1054 `_, - `#1063 `_, - `#1068 `_, - `#1074 `_, - `#1075 `_, - `#1079 `_, - `#1086 `_, - `#1088 `_, - `#1089 `_, - `#1094 `_, - `#1101 `_, - `#1102 `_, - `#1105 `_, - `#1107 `_, - `#1115 `_, - `#1117 `_, - `#1118 `_, - `#1120 `_, - `#1123 `_, - `#1139 `_, - `#1140 `_, - `#1143 `_, - `#1144 `_, - `#1150 `_, - `#1151 `_, - `#1152 `_, - `#1154 `_, - `#1156 `_, - `#1159 `_, - `#1175 `_, - `#1181 `_, - `#1186 `_, - `#1187 `_, - `#1191 `_, - `#1197 `_, - `#1200 `_, - `#1203 `_, - `#1205 `_, - `#1206 `_, - `#1213 `_, - `#1214 `_, - `#1217 `_, - `#1228 `_, - `#1230 `_, - `#1232 `_, - `#1235 `_, - `#1236 `_, - `#1240 `_). - Thanks `@DanielaE (Daniela Engert) `_, - `@mwinterb `_, - `@eliaskosunen (Elias Kosunen) `_, - `@morinmorin `_, - `@ricco19 (Brian Ricciardelli) `_, - `@waywardmonkeys (Bruce Mitchener) `_, - `@chronoxor (Ivan Shynkarenka) `_, - `@remyabel `_, - `@pauldreik (Paul Dreik) `_, - `@gsjaardema (Greg Sjaardema) `_, - `@rcane (Ronny Krüger) `_, - `@mocabe `_, - `@denchat `_, - `@cjdb (Christopher Di Bella) `_, - `@HazardyKnusperkeks (Björn Schäpers) `_, - `@vedranmiletic (Vedran Miletić) `_, - `@jackoalan (Jack Andersen) `_, - `@DaanDeMeyer (Daan De Meyer) `_, - `@starkmapper (Mark Stapper) `_. - -5.3.0 - 2018-12-28 ------------------- - -* Introduced experimental chrono formatting support: - - .. code:: c++ - - #include - - int main() { - using namespace std::literals::chrono_literals; - fmt::print("Default format: {} {}\n", 42s, 100ms); - fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); - } - - prints:: - - Default format: 42s 100ms - strftime-like format: 03:15:30 - -* Added experimental support for emphasis (bold, italic, underline, - strikethrough), colored output to a file stream, and improved colored - formatting API - (`#961 `_, - `#967 `_, - `#973 `_): - - .. code:: c++ - - #include - - int main() { - print(fg(fmt::color::crimson) | fmt::emphasis::bold, - "Hello, {}!\n", "world"); - print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) | - fmt::emphasis::underline, "Hello, {}!\n", "мир"); - print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, - "Hello, {}!\n", "世界"); - } - - prints the following on modern terminals with RGB color support: - - .. image:: https://user-images.githubusercontent.com/576385/ - 50405788-b66e7500-076e-11e9-9592-7324d1f951d8.png - - Thanks `@Rakete1111 (Nicolas) `_. - -* Added support for 4-bit terminal colors - (`#968 `_, - `#974 `_) - - .. code:: c++ - - #include - - int main() { - print(fg(fmt::terminal_color::red), "stop\n"); - } - - Note that these colors vary by terminal: - - .. image:: https://user-images.githubusercontent.com/576385/ - 50405925-dbfc7e00-0770-11e9-9b85-333fab0af9ac.png - - Thanks `@Rakete1111 (Nicolas) `_. - -* Parameterized formatting functions on the type of the format string - (`#880 `_, - `#881 `_, - `#883 `_, - `#885 `_, - `#897 `_, - `#920 `_). - Any object of type ``S`` that has an overloaded ``to_string_view(const S&)`` - returning ``fmt::string_view`` can be used as a format string: - - .. code:: c++ - - namespace my_ns { - inline string_view to_string_view(const my_string& s) { - return {s.data(), s.length()}; - } - } - - std::string message = fmt::format(my_string("The answer is {}."), 42); - - Thanks `@DanielaE (Daniela Engert) `_. - -* Made ``std::string_view`` work as a format string - (`#898 `_): - - .. code:: c++ - - auto message = fmt::format(std::string_view("The answer is {}."), 42); - - Thanks `@DanielaE (Daniela Engert) `_. - -* Added wide string support to compile-time format string checks - (`#924 `_): - - .. code:: c++ - - print(fmt(L"{:f}"), 42); // compile-time error: invalid type specifier - - Thanks `@XZiar `_. - -* Made colored print functions work with wide strings - (`#867 `_): - - .. code:: c++ - - #include - - int main() { - print(fg(fmt::color::red), L"{}\n", 42); - } - - Thanks `@DanielaE (Daniela Engert) `_. - -* Introduced experimental Unicode support - (`#628 `_, - `#891 `_): - - .. code:: c++ - - using namespace fmt::literals; - auto s = fmt::format("{:*^5}"_u, "🤡"_u); // s == "**🤡**"_u - -* Improved locale support: - - .. code:: c++ - - #include - - struct numpunct : std::numpunct { - protected: - char do_thousands_sep() const override { return '~'; } - }; - - std::locale loc; - auto s = fmt::format(std::locale(loc, new numpunct()), "{:n}", 1234567); - // s == "1~234~567" - -* Constrained formatting functions on proper iterator types - (`#921 `_). - Thanks `@DanielaE (Daniela Engert) `_. - -* Added ``make_printf_args`` and ``make_wprintf_args`` functions - (`#934 `_). - Thanks `@tnovotny `_. - -* Deprecated ``fmt::visit``, ``parse_context``, and ``wparse_context``. - Use ``fmt::visit_format_arg``, ``format_parse_context``, and - ``wformat_parse_context`` instead. - -* Removed undocumented ``basic_fixed_buffer`` which has been superseded by the - iterator-based API - (`#873 `_, - `#902 `_). - Thanks `@superfunc (hollywood programmer) `_. - -* Disallowed repeated leading zeros in an argument ID: - - .. code:: c++ - - fmt::print("{000}", 42); // error - -* Reintroduced support for gcc 4.4. - -* Fixed compilation on platforms with exotic ``double`` - (`#878 `_). - -* Improved documentation - (`#164 `_, - `#877 `_, - `#901 `_, - `#906 `_, - `#979 `_). - Thanks `@kookjr (Mathew Cucuzella) `_, - `@DarkDimius (Dmitry Petrashko) `_, - `@HecticSerenity `_. - -* Added pkgconfig support which makes it easier to consume the library from - meson and other build systems - (`#916 `_). - Thanks `@colemickens (Cole Mickens) `_. - -* Various build improvements - (`#909 `_, - `#926 `_, - `#937 `_, - `#953 `_, - `#959 `_). - Thanks `@tchaikov (Kefu Chai) `_, - `@luncliff (Park DongHa) `_, - `@AndreasSchoenle (Andreas Schönle) `_, - `@hotwatermorning `_, - `@Zefz (JohanJansen) `_. - -* Improved ``string_view`` construction performance - (`#914 `_). - Thanks `@gabime (Gabi Melman) `_. - -* Fixed non-matching char types - (`#895 `_). - Thanks `@DanielaE (Daniela Engert) `_. - -* Fixed ``format_to_n`` with ``std::back_insert_iterator`` - (`#913 `_). - Thanks `@DanielaE (Daniela Engert) `_. - -* Fixed locale-dependent formatting - (`#905 `_). - -* Fixed various compiler warnings and errors - (`#882 `_, - `#886 `_, - `#933 `_, - `#941 `_, - `#931 `_, - `#943 `_, - `#954 `_, - `#956 `_, - `#962 `_, - `#965 `_, - `#977 `_, - `#983 `_, - `#989 `_). - Thanks `@Luthaf (Guillaume Fraux) `_, - `@stevenhoving (Steven Hoving) `_, - `@christinaa (Kristina Brooks) `_, - `@lgritz (Larry Gritz) `_, - `@DanielaE (Daniela Engert) `_, - `@0x8000-0000 (Sign Bit) `_, - `@liuping1997 `_. - -5.2.1 - 2018-09-21 ------------------- - -* Fixed ``visit`` lookup issues on gcc 7 & 8 - (`#870 `_). - Thanks `@medithe `_. - -* Fixed linkage errors on older gcc. - -* Prevented ``fmt/range.h`` from specializing ``fmt::basic_string_view`` - (`#865 `_, - `#868 `_). - Thanks `@hhggit (dual) `_. - -* Improved error message when formatting unknown types - (`#872 `_). - Thanks `@foonathan (Jonathan Müller) `_, - -* Disabled templated user-defined literals when compiled under nvcc - (`#875 `_). - Thanks `@CandyGumdrop (Candy Gumdrop) `_, - -* Fixed ``format_to`` formatting to ``wmemory_buffer`` - (`#874 `_). - -5.2.0 - 2018-09-13 ------------------- - -* Optimized format string parsing and argument processing which resulted in up - to 5x speed up on long format strings and significant performance boost on - various benchmarks. For example, version 5.2 is 2.22x faster than 5.1 on - decimal integer formatting with ``format_to`` (macOS, clang-902.0.39.2): - - ================== ======= ======= - Method Time, s Speedup - ================== ======= ======= - fmt::format 5.1 0.58 - fmt::format 5.2 0.35 1.66x - fmt::format_to 5.1 0.51 - fmt::format_to 5.2 0.23 2.22x - sprintf 0.71 - std::to_string 1.01 - std::stringstream 1.73 - ================== ======= ======= - -* Changed the ``fmt`` macro from opt-out to opt-in to prevent name collisions. - To enable it define the ``FMT_STRING_ALIAS`` macro to 1 before including - ``fmt/format.h``: - - .. code:: c++ - - #define FMT_STRING_ALIAS 1 - #include - std::string answer = format(fmt("{}"), 42); - -* Added compile-time format string checks to ``format_to`` overload that takes - ``fmt::memory_buffer`` (`#783 `_): - - .. code:: c++ - - fmt::memory_buffer buf; - // Compile-time error: invalid type specifier. - fmt::format_to(buf, fmt("{:d}"), "foo"); - -* Moved experimental color support to ``fmt/color.h`` and enabled the - new API by default. The old API can be enabled by defining the - ``FMT_DEPRECATED_COLORS`` macro. - -* Added formatting support for types explicitly convertible to - ``fmt::string_view``: - - .. code:: c++ - - struct foo { - explicit operator fmt::string_view() const { return "foo"; } - }; - auto s = format("{}", foo()); - - In particular, this makes formatting function work with - ``folly::StringPiece``. - -* Implemented preliminary support for ``char*_t`` by replacing the ``format`` - function overloads with a single function template parameterized on the string - type. - -* Added support for dynamic argument lists - (`#814 `_, - `#819 `_). - Thanks `@MikePopoloski (Michael Popoloski) - `_. - -* Reduced executable size overhead for embedded targets using newlib nano by - making locale dependency optional - (`#839 `_). - Thanks `@teajay-fr (Thomas Benard) `_. - -* Keep ``noexcept`` specifier when exceptions are disabled - (`#801 `_, - `#810 `_). - Thanks `@qis (Alexej Harm) `_. - -* Fixed formatting of user-defined types providing ``operator<<`` with - ``format_to_n`` - (`#806 `_). - Thanks `@mkurdej (Marek Kurdej) `_. - -* Fixed dynamic linkage of new symbols - (`#808 `_). - -* Fixed global initialization issue - (`#807 `_): - - .. code:: c++ - - // This works on compilers with constexpr support. - static const std::string answer = fmt::format("{}", 42); - -* Fixed various compiler warnings and errors - (`#804 `_, - `#809 `_, - `#811 `_, - `#822 `_, - `#827 `_, - `#830 `_, - `#838 `_, - `#843 `_, - `#844 `_, - `#851 `_, - `#852 `_, - `#854 `_). - Thanks `@henryiii (Henry Schreiner) `_, - `@medithe `_, and - `@eliasdaler (Elias Daler) `_. - -5.1.0 - 2018-07-05 ------------------- - -* Added experimental support for RGB color output enabled with - the ``FMT_EXTENDED_COLORS`` macro: - - .. code:: c++ - - #define FMT_EXTENDED_COLORS - #define FMT_HEADER_ONLY // or compile fmt with FMT_EXTENDED_COLORS defined - #include - - fmt::print(fmt::color::steel_blue, "Some beautiful text"); - - The old API (the ``print_colored`` and ``vprint_colored`` functions and the - ``color`` enum) is now deprecated. - (`#762 `_ - `#767 `_). - thanks `@Remotion (Remo) `_. - -* Added quotes to strings in ranges and tuples - (`#766 `_). - Thanks `@Remotion (Remo) `_. - -* Made ``format_to`` work with ``basic_memory_buffer`` - (`#776 `_). - -* Added ``vformat_to_n`` and ``wchar_t`` overload of ``format_to_n`` - (`#764 `_, - `#769 `_). - -* Made ``is_range`` and ``is_tuple_like`` part of public (experimental) API - to allow specialization for user-defined types - (`#751 `_, - `#759 `_). - Thanks `@drrlvn (Dror Levin) `_. - -* Added more compilers to continuous integration and increased ``FMT_PEDANTIC`` - warning levels - (`#736 `_). - Thanks `@eliaskosunen (Elias Kosunen) `_. - -* Fixed compilation with MSVC 2013. - -* Fixed handling of user-defined types in ``format_to`` - (`#793 `_). - -* Forced linking of inline ``vformat`` functions into the library - (`#795 `_). - -* Fixed incorrect call to on_align in ``'{:}='`` - (`#750 `_). - -* Fixed floating-point formatting to a non-back_insert_iterator with sign & - numeric alignment specified - (`#756 `_). - -* Fixed formatting to an array with ``format_to_n`` - (`#778 `_). - -* Fixed formatting of more than 15 named arguments - (`#754 `_). - -* Fixed handling of compile-time strings when including ``fmt/ostream.h``. - (`#768 `_). - -* Fixed various compiler warnings and errors - (`#742 `_, - `#748 `_, - `#752 `_, - `#770 `_, - `#775 `_, - `#779 `_, - `#780 `_, - `#790 `_, - `#792 `_, - `#800 `_). - Thanks `@Remotion (Remo) `_, - `@gabime (Gabi Melman) `_, - `@foonathan (Jonathan Müller) `_, - `@Dark-Passenger (Dhruv Paranjape) `_, and - `@0x8000-0000 (Sign Bit) `_. - -5.0.0 - 2018-05-21 ------------------- - -* Added a requirement for partial C++11 support, most importantly variadic - templates and type traits, and dropped ``FMT_VARIADIC_*`` emulation macros. - Variadic templates are available since GCC 4.4, Clang 2.9 and MSVC 18.0 (2013). - For older compilers use {fmt} `version 4.x - `_ which continues to be - maintained and works with C++98 compilers. - -* Renamed symbols to follow standard C++ naming conventions and proposed a subset - of the library for standardization in `P0645R2 Text Formatting - `_. - -* Implemented ``constexpr`` parsing of format strings and `compile-time format - string checks - `_. For - example - - .. code:: c++ - - #include - - std::string s = format(fmt("{:d}"), "foo"); - - gives a compile-time error because ``d`` is an invalid specifier for strings - (`godbolt `__):: - - ... - :4:19: note: in instantiation of function template specialization 'fmt::v5::format' requested here - std::string s = format(fmt("{:d}"), "foo"); - ^ - format.h:1337:13: note: non-constexpr function 'on_error' cannot be used in a constant expression - handler.on_error("invalid type specifier"); - - Compile-time checks require relaxed ``constexpr`` (C++14 feature) support. If - the latter is not available, checks will be performed at runtime. - -* Separated format string parsing and formatting in the extension API to enable - compile-time format string processing. For example - - .. code:: c++ - - struct Answer {}; - - namespace fmt { - template <> - struct formatter { - constexpr auto parse(parse_context& ctx) { - auto it = ctx.begin(); - spec = *it; - if (spec != 'd' && spec != 's') - throw format_error("invalid specifier"); - return ++it; - } - - template - auto format(Answer, FormatContext& ctx) { - return spec == 's' ? - format_to(ctx.begin(), "{}", "fourty-two") : - format_to(ctx.begin(), "{}", 42); - } - - char spec = 0; - }; - } - - std::string s = format(fmt("{:x}"), Answer()); - - gives a compile-time error due to invalid format specifier (`godbolt - `__):: - - ... - :12:45: error: expression '' is not a constant expression - throw format_error("invalid specifier"); - -* Added `iterator support - `_: - - .. code:: c++ - - #include - #include - - std::vector out; - fmt::format_to(std::back_inserter(out), "{}", 42); - -* Added the `format_to_n - `_ - function that restricts the output to the specified number of characters - (`#298 `_): - - .. code:: c++ - - char out[4]; - fmt::format_to_n(out, sizeof(out), "{}", 12345); - // out == "1234" (without terminating '\0') - -* Added the `formatted_size - `_ - function for computing the output size: - - .. code:: c++ - - #include - - auto size = fmt::formatted_size("{}", 12345); // size == 5 - -* Improved compile times by reducing dependencies on standard headers and - providing a lightweight `core API `_: - - .. code:: c++ - - #include - - fmt::print("The answer is {}.", 42); - - See `Compile time and code bloat - `_. - -* Added the `make_format_args - `_ - function for capturing formatting arguments: - - .. code:: c++ - - // Prints formatted error message. - void vreport_error(const char *format, fmt::format_args args) { - fmt::print("Error: "); - fmt::vprint(format, args); - } - template - void report_error(const char *format, const Args & ... args) { - vreport_error(format, fmt::make_format_args(args...)); - } - -* Added the ``make_printf_args`` function for capturing ``printf`` arguments - (`#687 `_, - `#694 `_). - Thanks `@Kronuz (Germán Méndez Bravo) `_. - -* Added prefix ``v`` to non-variadic functions taking ``format_args`` to - distinguish them from variadic ones: - - .. code:: c++ - - std::string vformat(string_view format_str, format_args args); - - template - std::string format(string_view format_str, const Args & ... args); - -* Added experimental support for formatting ranges, containers and tuple-like - types in ``fmt/ranges.h`` (`#735 `_): - - .. code:: c++ - - #include - - std::vector v = {1, 2, 3}; - fmt::print("{}", v); // prints {1, 2, 3} - - Thanks `@Remotion (Remo) `_. - -* Implemented ``wchar_t`` date and time formatting - (`#712 `_): - - .. code:: c++ - - #include - - std::time_t t = std::time(nullptr); - auto s = fmt::format(L"The date is {:%Y-%m-%d}.", *std::localtime(&t)); - - Thanks `@DanielaE (Daniela Engert) `_. - -* Provided more wide string overloads - (`#724 `_). - Thanks `@DanielaE (Daniela Engert) `_. - -* Switched from a custom null-terminated string view class to ``string_view`` - in the format API and provided ``fmt::string_view`` which implements a subset - of ``std::string_view`` API for pre-C++17 systems. - -* Added support for ``std::experimental::string_view`` - (`#607 `_): - - .. code:: c++ - - #include - #include - - fmt::print("{}", std::experimental::string_view("foo")); - - Thanks `@virgiliofornazin (Virgilio Alexandre Fornazin) - `__. - -* Allowed mixing named and automatic arguments: - - .. code:: c++ - - fmt::format("{} {two}", 1, fmt::arg("two", 2)); - -* Removed the write API in favor of the `format API - `_ with compile-time handling of - format strings. - -* Disallowed formatting of multibyte strings into a wide character target - (`#606 `_). - -* Improved documentation - (`#515 `_, - `#614 `_, - `#617 `_, - `#661 `_, - `#680 `_). - Thanks `@ibell (Ian Bell) `_, - `@mihaitodor (Mihai Todor) `_, and - `@johnthagen `_. - -* Implemented more efficient handling of large number of format arguments. - -* Introduced an inline namespace for symbol versioning. - -* Added debug postfix ``d`` to the ``fmt`` library name - (`#636 `_). - -* Removed unnecessary ``fmt/`` prefix in includes - (`#397 `_). - Thanks `@chronoxor (Ivan Shynkarenka) `_. - -* Moved ``fmt/*.h`` to ``include/fmt/*.h`` to prevent irrelevant files and - directories appearing on the include search paths when fmt is used as a - subproject and moved source files to the ``src`` directory. - -* Added qmake project file ``support/fmt.pro`` - (`#641 `_). - Thanks `@cowo78 (Giuseppe Corbelli) `_. - -* Added Gradle build file ``support/build.gradle`` - (`#649 `_). - Thanks `@luncliff (Park DongHa) `_. - -* Removed ``FMT_CPPFORMAT`` CMake option. - -* Fixed a name conflict with the macro ``CHAR_WIDTH`` in glibc - (`#616 `_). - Thanks `@aroig (Abdó Roig-Maranges) `_. - -* Fixed handling of nested braces in ``fmt::join`` - (`#638 `_). - -* Added ``SOURCELINK_SUFFIX`` for compatibility with Sphinx 1.5 - (`#497 `_). - Thanks `@ginggs (Graham Inggs) `_. - -* Added a missing ``inline`` in the header-only mode - (`#626 `_). - Thanks `@aroig (Abdó Roig-Maranges) `_. - -* Fixed various compiler warnings - (`#640 `_, - `#656 `_, - `#679 `_, - `#681 `_, - `#705 `__, - `#715 `_, - `#717 `_, - `#720 `_, - `#723 `_, - `#726 `_, - `#730 `_, - `#739 `_). - Thanks `@peterbell10 `_, - `@LarsGullik `_, - `@foonathan (Jonathan Müller) `_, - `@eliaskosunen (Elias Kosunen) `_, - `@christianparpart (Christian Parpart) `_, - `@DanielaE (Daniela Engert) `_, - and `@mwinterb `_. - -* Worked around an MSVC bug and fixed several warnings - (`#653 `_). - Thanks `@alabuzhev (Alex Alabuzhev) `_. - -* Worked around GCC bug 67371 - (`#682 `_). - -* Fixed compilation with ``-fno-exceptions`` - (`#655 `_). - Thanks `@chenxiaolong (Andrew Gunnerson) `_. - -* Made ``constexpr remove_prefix`` gcc version check tighter - (`#648 `_). - -* Renamed internal type enum constants to prevent collision with poorly written - C libraries (`#644 `_). - -* Added detection of ``wostream operator<<`` - (`#650 `_). - -* Fixed compilation on OpenBSD - (`#660 `_). - Thanks `@hubslave `_. - -* Fixed compilation on FreeBSD 12 - (`#732 `_). - Thanks `@dankm `_. - -* Fixed compilation when there is a mismatch between ``-std`` options between - the library and user code - (`#664 `_). - -* Fixed compilation with GCC 7 and ``-std=c++11`` - (`#734 `_). - -* Improved generated binary code on GCC 7 and older - (`#668 `_). - -* Fixed handling of numeric alignment with no width - (`#675 `_). - -* Fixed handling of empty strings in UTF8/16 converters - (`#676 `_). - Thanks `@vgalka-sl (Vasili Galka) `_. - -* Fixed formatting of an empty ``string_view`` - (`#689 `_). - -* Fixed detection of ``string_view`` on libc++ - (`#686 `_). - -* Fixed DLL issues (`#696 `_). - Thanks `@sebkoenig `_. - -* Fixed compile checks for mixing narrow and wide strings - (`#690 `_). - -* Disabled unsafe implicit conversion to ``std::string`` - (`#729 `_). - -* Fixed handling of reused format specs (as in ``fmt::join``) for pointers - (`#725 `_). - Thanks `@mwinterb `_. - -* Fixed installation of ``fmt/ranges.h`` - (`#738 `_). - Thanks `@sv1990 `_. - -4.1.0 - 2017-12-20 ------------------- - -* Added ``fmt::to_wstring()`` in addition to ``fmt::to_string()`` - (`#559 `_). - Thanks `@alabuzhev (Alex Alabuzhev) `_. - -* Added support for C++17 ``std::string_view`` - (`#571 `_ and - `#578 `_). - Thanks `@thelostt (Mário Feroldi) `_ and - `@mwinterb `_. - -* Enabled stream exceptions to catch errors - (`#581 `_). - Thanks `@crusader-mike `_. - -* Allowed formatting of class hierarchies with ``fmt::format_arg()`` - (`#547 `_). - Thanks `@rollbear (Björn Fahller) `_. - -* Removed limitations on character types - (`#563 `_). - Thanks `@Yelnats321 (Elnar Dakeshov) `_. - -* Conditionally enabled use of ``std::allocator_traits`` - (`#583 `_). - Thanks `@mwinterb `_. - -* Added support for ``const`` variadic member function emulation with - ``FMT_VARIADIC_CONST`` (`#591 `_). - Thanks `@ludekvodicka (Ludek Vodicka) `_. - -* Various bugfixes: bad overflow check, unsupported implicit type conversion - when determining formatting function, test segfaults - (`#551 `_), ill-formed macros - (`#542 `_) and ambiguous overloads - (`#580 `_). - Thanks `@xylosper (Byoung-young Lee) `_. - -* Prevented warnings on MSVC (`#605 `_, - `#602 `_, and - `#545 `_), - clang (`#582 `_), - GCC (`#573 `_), - various conversion warnings (`#609 `_, - `#567 `_, - `#553 `_ and - `#553 `_), and added ``override`` and - ``[[noreturn]]`` (`#549 `_ and - `#555 `_). - Thanks `@alabuzhev (Alex Alabuzhev) `_, - `@virgiliofornazin (Virgilio Alexandre Fornazin) - `_, - `@alexanderbock (Alexander Bock) `_, - `@yumetodo `_, - `@VaderY (Császár Mátyás) `_, - `@jpcima (JP Cimalando) `_, - `@thelostt (Mário Feroldi) `_, and - `@Manu343726 (Manu Sánchez) `_. - -* Improved CMake: Used ``GNUInstallDirs`` to set installation location - (`#610 `_) and fixed warnings - (`#536 `_ and - `#556 `_). - Thanks `@mikecrowe (Mike Crowe) `_, - `@evgen231 `_ and - `@henryiii (Henry Schreiner) `_. - -4.0.0 - 2017-06-27 ------------------- - -* Removed old compatibility headers ``cppformat/*.h`` and CMake options - (`#527 `_). - Thanks `@maddinat0r (Alex Martin) `_. - -* Added ``string.h`` containing ``fmt::to_string()`` as alternative to - ``std::to_string()`` as well as other string writer functionality - (`#326 `_ and - `#441 `_): - - .. code:: c++ - - #include "fmt/string.h" - - std::string answer = fmt::to_string(42); - - Thanks to `@glebov-andrey (Andrey Glebov) - `_. - -* Moved ``fmt::printf()`` to new ``printf.h`` header and allowed ``%s`` as - generic specifier (`#453 `_), - made ``%.f`` more conformant to regular ``printf()`` - (`#490 `_), added custom writer - support (`#476 `_) and implemented - missing custom argument formatting - (`#339 `_ and - `#340 `_): - - .. code:: c++ - - #include "fmt/printf.h" - - // %s format specifier can be used with any argument type. - fmt::printf("%s", 42); - - Thanks `@mojoBrendan `_, - `@manylegged (Arthur Danskin) `_ and - `@spacemoose (Glen Stark) `_. - See also `#360 `_, - `#335 `_ and - `#331 `_. - -* Added ``container.h`` containing a ``BasicContainerWriter`` - to write to containers like ``std::vector`` - (`#450 `_). - Thanks `@polyvertex (Jean-Charles Lefebvre) `_. - -* Added ``fmt::join()`` function that takes a range and formats - its elements separated by a given string - (`#466 `_): - - .. code:: c++ - - #include "fmt/format.h" - - std::vector v = {1.2, 3.4, 5.6}; - // Prints "(+01.20, +03.40, +05.60)". - fmt::print("({:+06.2f})", fmt::join(v.begin(), v.end(), ", ")); - - Thanks `@olivier80 `_. - -* Added support for custom formatting specifications to simplify customization - of built-in formatting (`#444 `_). - Thanks `@polyvertex (Jean-Charles Lefebvre) `_. - See also `#439 `_. - -* Added ``fmt::format_system_error()`` for error code formatting - (`#323 `_ and - `#526 `_). - Thanks `@maddinat0r (Alex Martin) `_. - -* Added thread-safe ``fmt::localtime()`` and ``fmt::gmtime()`` - as replacement for the standard version to ``time.h`` - (`#396 `_). - Thanks `@codicodi `_. - -* Internal improvements to ``NamedArg`` and ``ArgLists`` - (`#389 `_ and - `#390 `_). - Thanks `@chronoxor `_. - -* Fixed crash due to bug in ``FormatBuf`` - (`#493 `_). - Thanks `@effzeh `_. See also - `#480 `_ and - `#491 `_. - -* Fixed handling of wide strings in ``fmt::StringWriter``. - -* Improved compiler error messages - (`#357 `_). - -* Fixed various warnings and issues with various compilers - (`#494 `_, - `#499 `_, - `#483 `_, - `#485 `_, - `#482 `_, - `#475 `_, - `#473 `_ and - `#414 `_). - Thanks `@chronoxor `_, - `@zhaohuaxishi `_, - `@pkestene (Pierre Kestener) `_, - `@dschmidt (Dominik Schmidt) `_ and - `@0x414c (Alexey Gorishny) `_ . - -* Improved CMake: targets are now namespaced - (`#511 `_ and - `#513 `_), supported header-only - ``printf.h`` (`#354 `_), fixed issue - with minimal supported library subset - (`#418 `_, - `#419 `_ and - `#420 `_). - Thanks `@bjoernthiel (Bjoern Thiel) `_, - `@niosHD (Mario Werner) `_, - `@LogicalKnight (Sean LK) `_ and - `@alabuzhev (Alex Alabuzhev) `_. - -* Improved documentation. Thanks to - `@pwm1234 (Phil) `_ for - `#393 `_. - -3.0.2 - 2017-06-14 ------------------- - -* Added ``FMT_VERSION`` macro - (`#411 `_). - -* Used ``FMT_NULL`` instead of literal ``0`` - (`#409 `_). - Thanks `@alabuzhev (Alex Alabuzhev) `_. - -* Added extern templates for ``format_float`` - (`#413 `_). - -* Fixed implicit conversion issue - (`#507 `_). - -* Fixed signbit detection (`#423 `_). - -* Fixed naming collision (`#425 `_). - -* Fixed missing intrinsic for C++/CLI - (`#457 `_). - Thanks `@calumr (Calum Robinson) `_ - -* Fixed Android detection (`#458 `_). - Thanks `@Gachapen (Magnus Bjerke Vik) `_. - -* Use lean ``windows.h`` if not in header-only mode - (`#503 `_). - Thanks `@Quentin01 (Quentin Buathier) `_. - -* Fixed issue with CMake exporting C++11 flag - (`#445 `_). - Thanks `@EricWF (Eric) `_. - -* Fixed issue with nvcc and MSVC compiler bug and MinGW - (`#505 `_). - -* Fixed DLL issues (`#469 `_ and - `#502 `_). - Thanks `@richardeakin (Richard Eakin) `_ and - `@AndreasSchoenle (Andreas Schönle) `_. - -* Fixed test compilation under FreeBSD - (`#433 `_). - -* Fixed various warnings (`#403 `_, - `#410 `_ and - `#510 `_). - Thanks `@Lecetem `_, - `@chenhayat (Chen Hayat) `_ and - `@trozen `_. - -* Worked around a broken ``__builtin_clz`` in clang with MS codegen - (`#519 `_). - -* Removed redundant include - (`#479 `_). - -* Fixed documentation issues. - -3.0.1 - 2016-11-01 ------------------- -* Fixed handling of thousands separator - (`#353 `_). - -* Fixed handling of ``unsigned char`` strings - (`#373 `_). - -* Corrected buffer growth when formatting time - (`#367 `_). - -* Removed warnings under MSVC and clang - (`#318 `_, - `#250 `_, also merged - `#385 `_ and - `#361 `_). - Thanks `@jcelerier (Jean-Michaël Celerier) `_ - and `@nmoehrle (Nils Moehrle) `_. - -* Fixed compilation issues under Android - (`#327 `_, - `#345 `_ and - `#381 `_), - FreeBSD (`#358 `_), - Cygwin (`#388 `_), - MinGW (`#355 `_) as well as other - issues (`#350 `_, - `#366 `_, - `#348 `_, - `#402 `_, - `#405 `_). - Thanks to `@dpantele (Dmitry) `_, - `@hghwng (Hugh Wang) `_, - `@arvedarved (Tilman Keskinöz) `_, - `@LogicalKnight (Sean) `_ and - `@JanHellwig (Jan Hellwig) `_. - -* Fixed some documentation issues and extended specification - (`#320 `_, - `#333 `_, - `#347 `_, - `#362 `_). - Thanks to `@smellman (Taro Matsuzawa aka. btm) - `_. - -3.0.0 - 2016-05-07 ------------------- - -* The project has been renamed from C++ Format (cppformat) to fmt for - consistency with the used namespace and macro prefix - (`#307 `_). - Library headers are now located in the ``fmt`` directory: - - .. code:: c++ - - #include "fmt/format.h" - - Including ``format.h`` from the ``cppformat`` directory is deprecated - but works via a proxy header which will be removed in the next major version. - - The documentation is now available at https://fmt.dev. - -* Added support for `strftime `_-like - `date and time formatting `_ - (`#283 `_): - - .. code:: c++ - - #include "fmt/time.h" - - std::time_t t = std::time(nullptr); - // Prints "The date is 2016-04-29." (with the current date) - fmt::print("The date is {:%Y-%m-%d}.", *std::localtime(&t)); - -* ``std::ostream`` support including formatting of user-defined types that provide - overloaded ``operator<<`` has been moved to ``fmt/ostream.h``: - - .. code:: c++ - - #include "fmt/ostream.h" - - class Date { - int year_, month_, day_; - public: - Date(int year, int month, int day) : year_(year), month_(month), day_(day) {} - - friend std::ostream &operator<<(std::ostream &os, const Date &d) { - return os << d.year_ << '-' << d.month_ << '-' << d.day_; - } - }; - - std::string s = fmt::format("The date is {}", Date(2012, 12, 9)); - // s == "The date is 2012-12-9" - -* Added support for `custom argument formatters - `_ - (`#235 `_). - -* Added support for locale-specific integer formatting with the ``n`` specifier - (`#305 `_): - - .. code:: c++ - - std::setlocale(LC_ALL, "en_US.utf8"); - fmt::print("cppformat: {:n}\n", 1234567); // prints 1,234,567 - -* Sign is now preserved when formatting an integer with an incorrect ``printf`` - format specifier (`#265 `_): - - .. code:: c++ - - fmt::printf("%lld", -42); // prints -42 - - Note that it would be an undefined behavior in ``std::printf``. - -* Length modifiers such as ``ll`` are now optional in printf formatting - functions and the correct type is determined automatically - (`#255 `_): - - .. code:: c++ - - fmt::printf("%d", std::numeric_limits::max()); - - Note that it would be an undefined behavior in ``std::printf``. - -* Added initial support for custom formatters - (`#231 `_). - -* Fixed detection of user-defined literal support on Intel C++ compiler - (`#311 `_, - `#312 `_). - Thanks to `@dean0x7d (Dean Moldovan) `_ and - `@speth (Ray Speth) `_. - -* Reduced compile time - (`#243 `_, - `#249 `_, - `#317 `_): - - .. image:: https://cloud.githubusercontent.com/assets/4831417/11614060/ - b9e826d2-9c36-11e5-8666-d4131bf503ef.png - - .. image:: https://cloud.githubusercontent.com/assets/4831417/11614080/ - 6ac903cc-9c37-11e5-8165-26df6efae364.png - - Thanks to `@dean0x7d (Dean Moldovan) `_. - -* Compile test fixes (`#313 `_). - Thanks to `@dean0x7d (Dean Moldovan) `_. - -* Documentation fixes (`#239 `_, - `#248 `_, - `#252 `_, - `#258 `_, - `#260 `_, - `#301 `_, - `#309 `_). - Thanks to `@ReadmeCritic `_ - `@Gachapen (Magnus Bjerke Vik) `_ and - `@jwilk (Jakub Wilk) `_. - -* Fixed compiler and sanitizer warnings - (`#244 `_, - `#256 `_, - `#259 `_, - `#263 `_, - `#274 `_, - `#277 `_, - `#286 `_, - `#291 `_, - `#296 `_, - `#308 `_) - Thanks to `@mwinterb `_, - `@pweiskircher (Patrik Weiskircher) `_, - `@Naios `_. - -* Improved compatibility with Windows Store apps - (`#280 `_, - `#285 `_) - Thanks to `@mwinterb `_. - -* Added tests of compatibility with older C++ standards - (`#273 `_). - Thanks to `@niosHD `_. - -* Fixed Android build (`#271 `_). - Thanks to `@newnon `_. - -* Changed ``ArgMap`` to be backed by a vector instead of a map. - (`#261 `_, - `#262 `_). - Thanks to `@mwinterb `_. - -* Added ``fprintf`` overload that writes to a ``std::ostream`` - (`#251 `_). - Thanks to `nickhutchinson (Nicholas Hutchinson) `_. - -* Export symbols when building a Windows DLL - (`#245 `_). - Thanks to `macdems (Maciek Dems) `_. - -* Fixed compilation on Cygwin (`#304 `_). - -* Implemented a workaround for a bug in Apple LLVM version 4.2 of clang - (`#276 `_). - -* Implemented a workaround for Google Test bug - `#705 `_ on gcc 6 - (`#268 `_). - Thanks to `octoploid `_. - -* Removed Biicode support because the latter has been discontinued. - -2.1.1 - 2016-04-11 ------------------- - -* The install location for generated CMake files is now configurable via - the ``FMT_CMAKE_DIR`` CMake variable - (`#299 `_). - Thanks to `@niosHD `_. - -* Documentation fixes (`#252 `_). - -2.1.0 - 2016-03-21 ------------------- - -* Project layout and build system improvements - (`#267 `_): - - * The code have been moved to the ``cppformat`` directory. - Including ``format.h`` from the top-level directory is deprecated - but works via a proxy header which will be removed in the next - major version. - - * C++ Format CMake targets now have proper interface definitions. - - * Installed version of the library now supports the header-only - configuration. - - * Targets ``doc``, ``install``, and ``test`` are now disabled if C++ Format - is included as a CMake subproject. They can be enabled by setting - ``FMT_DOC``, ``FMT_INSTALL``, and ``FMT_TEST`` in the parent project. - - Thanks to `@niosHD `_. - -2.0.1 - 2016-03-13 ------------------- - -* Improved CMake find and package support - (`#264 `_). - Thanks to `@niosHD `_. - -* Fix compile error with Android NDK and mingw32 - (`#241 `_). - Thanks to `@Gachapen (Magnus Bjerke Vik) `_. - -* Documentation fixes - (`#248 `_, - `#260 `_). - -2.0.0 - 2015-12-01 ------------------- - -General -~~~~~~~ - -* [Breaking] Named arguments - (`#169 `_, - `#173 `_, - `#174 `_): - - .. code:: c++ - - fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); - - Thanks to `@jamboree `_. - -* [Experimental] User-defined literals for format and named arguments - (`#204 `_, - `#206 `_, - `#207 `_): - - .. code:: c++ - - using namespace fmt::literals; - fmt::print("The answer is {answer}.", "answer"_a=42); - - Thanks to `@dean0x7d (Dean Moldovan) `_. - -* [Breaking] Formatting of more than 16 arguments is now supported when using - variadic templates - (`#141 `_). - Thanks to `@Shauren `_. - -* Runtime width specification - (`#168 `_): - - .. code:: c++ - - fmt::format("{0:{1}}", 42, 5); // gives " 42" - - Thanks to `@jamboree `_. - -* [Breaking] Enums are now formatted with an overloaded ``std::ostream`` insertion - operator (``operator<<``) if available - (`#232 `_). - -* [Breaking] Changed default ``bool`` format to textual, "true" or "false" - (`#170 `_): - - .. code:: c++ - - fmt::print("{}", true); // prints "true" - - To print ``bool`` as a number use numeric format specifier such as ``d``: - - .. code:: c++ - - fmt::print("{:d}", true); // prints "1" - -* ``fmt::printf`` and ``fmt::sprintf`` now support formatting of ``bool`` with the - ``%s`` specifier giving textual output, "true" or "false" - (`#223 `_): - - .. code:: c++ - - fmt::printf("%s", true); // prints "true" - - Thanks to `@LarsGullik `_. - -* [Breaking] ``signed char`` and ``unsigned char`` are now formatted as integers by default - (`#217 `_). - -* [Breaking] Pointers to C strings can now be formatted with the ``p`` specifier - (`#223 `_): - - .. code:: c++ - - fmt::print("{:p}", "test"); // prints pointer value - - Thanks to `@LarsGullik `_. - -* [Breaking] ``fmt::printf`` and ``fmt::sprintf`` now print null pointers as ``(nil)`` - and null strings as ``(null)`` for consistency with glibc - (`#226 `_). - Thanks to `@LarsGullik `_. - -* [Breaking] ``fmt::(s)printf`` now supports formatting of objects of user-defined types - that provide an overloaded ``std::ostream`` insertion operator (``operator<<``) - (`#201 `_): - - .. code:: c++ - - fmt::printf("The date is %s", Date(2012, 12, 9)); - -* [Breaking] The ``Buffer`` template is now part of the public API and can be used - to implement custom memory buffers - (`#140 `_). - Thanks to `@polyvertex (Jean-Charles Lefebvre) `_. - -* [Breaking] Improved compatibility between ``BasicStringRef`` and - `std::experimental::basic_string_view - `_ - (`#100 `_, - `#159 `_, - `#183 `_): - - - Comparison operators now compare string content, not pointers - - ``BasicStringRef::c_str`` replaced by ``BasicStringRef::data`` - - ``BasicStringRef`` is no longer assumed to be null-terminated - - References to null-terminated strings are now represented by a new class, - ``BasicCStringRef``. - -* Dependency on pthreads introduced by Google Test is now optional - (`#185 `_). - -* New CMake options ``FMT_DOC``, ``FMT_INSTALL`` and ``FMT_TEST`` to control - generation of ``doc``, ``install`` and ``test`` targets respectively, on by default - (`#197 `_, - `#198 `_, - `#200 `_). - Thanks to `@maddinat0r (Alex Martin) `_. - -* ``noexcept`` is now used when compiling with MSVC2015 - (`#215 `_). - Thanks to `@dmkrepo (Dmitriy) `_. - -* Added an option to disable use of ``windows.h`` when ``FMT_USE_WINDOWS_H`` - is defined as 0 before including ``format.h`` - (`#171 `_). - Thanks to `@alfps (Alf P. Steinbach) `_. - -* [Breaking] ``windows.h`` is now included with ``NOMINMAX`` unless - ``FMT_WIN_MINMAX`` is defined. This is done to prevent breaking code using - ``std::min`` and ``std::max`` and only affects the header-only configuration - (`#152 `_, - `#153 `_, - `#154 `_). - Thanks to `@DevO2012 `_. - -* Improved support for custom character types - (`#171 `_). - Thanks to `@alfps (Alf P. Steinbach) `_. - -* Added an option to disable use of IOStreams when ``FMT_USE_IOSTREAMS`` - is defined as 0 before including ``format.h`` - (`#205 `_, - `#208 `_). - Thanks to `@JodiTheTigger `_. - -* Improved detection of ``isnan``, ``isinf`` and ``signbit``. - -Optimization -~~~~~~~~~~~~ - -* Made formatting of user-defined types more efficient with a custom stream buffer - (`#92 `_, - `#230 `_). - Thanks to `@NotImplemented `_. - -* Further improved performance of ``fmt::Writer`` on integer formatting - and fixed a minor regression. Now it is ~7% faster than ``karma::generate`` - on Karma's benchmark - (`#186 `_). - -* [Breaking] Reduced `compiled code size - `_ - (`#143 `_, - `#149 `_). - -Distribution -~~~~~~~~~~~~ - -* [Breaking] Headers are now installed in - ``${CMAKE_INSTALL_PREFIX}/include/cppformat`` - (`#178 `_). - Thanks to `@jackyf (Eugene V. Lyubimkin) `_. - -* [Breaking] Changed the library name from ``format`` to ``cppformat`` - for consistency with the project name and to avoid potential conflicts - (`#178 `_). - Thanks to `@jackyf (Eugene V. Lyubimkin) `_. - -* C++ Format is now available in `Debian `_ GNU/Linux - (`stretch `_, - `sid `_) and - derived distributions such as - `Ubuntu `_ 15.10 and later - (`#155 `_):: - - $ sudo apt-get install libcppformat1-dev - - Thanks to `@jackyf (Eugene V. Lyubimkin) `_. - -* `Packages for Fedora and RHEL `_ - are now available. Thanks to Dave Johansen. - -* C++ Format can now be installed via `Homebrew `_ on OS X - (`#157 `_):: - - $ brew install cppformat - - Thanks to `@ortho `_, Anatoliy Bulukin. - -Documentation -~~~~~~~~~~~~~ - -* Migrated from ReadTheDocs to GitHub Pages for better responsiveness - and reliability - (`#128 `_). - New documentation address is http://cppformat.github.io/. - - -* Added `Building the documentation - `_ - section to the documentation. - -* Documentation build script is now compatible with Python 3 and newer pip versions. - (`#189 `_, - `#209 `_). - Thanks to `@JodiTheTigger `_ and - `@xentec `_. - -* Documentation fixes and improvements - (`#36 `_, - `#75 `_, - `#125 `_, - `#160 `_, - `#161 `_, - `#162 `_, - `#165 `_, - `#210 `_). - Thanks to `@syohex (Syohei YOSHIDA) `_ and - bug reporters. - -* Fixed out-of-tree documentation build - (`#177 `_). - Thanks to `@jackyf (Eugene V. Lyubimkin) `_. - -Fixes -~~~~~ - -* Fixed ``initializer_list`` detection - (`#136 `_). - Thanks to `@Gachapen (Magnus Bjerke Vik) `_. - -* [Breaking] Fixed formatting of enums with numeric format specifiers in - ``fmt::(s)printf`` - (`#131 `_, - `#139 `_): - - .. code:: c++ - - enum { ANSWER = 42 }; - fmt::printf("%d", ANSWER); - - Thanks to `@Naios `_. - -* Improved compatibility with old versions of MinGW - (`#129 `_, - `#130 `_, - `#132 `_). - Thanks to `@cstamford (Christopher Stamford) `_. - -* Fixed a compile error on MSVC with disabled exceptions - (`#144 `_). - -* Added a workaround for broken implementation of variadic templates in MSVC2012 - (`#148 `_). - -* Placed the anonymous namespace within ``fmt`` namespace for the header-only - configuration - (`#171 `_). - Thanks to `@alfps (Alf P. Steinbach) `_. - -* Fixed issues reported by Coverity Scan - (`#187 `_, - `#192 `_). - -* Implemented a workaround for a name lookup bug in MSVC2010 - (`#188 `_). - -* Fixed compiler warnings - (`#95 `_, - `#96 `_, - `#114 `_, - `#135 `_, - `#142 `_, - `#145 `_, - `#146 `_, - `#158 `_, - `#163 `_, - `#175 `_, - `#190 `_, - `#191 `_, - `#194 `_, - `#196 `_, - `#216 `_, - `#218 `_, - `#220 `_, - `#229 `_, - `#233 `_, - `#234 `_, - `#236 `_, - `#281 `_, - `#289 `_). - Thanks to `@seanmiddleditch (Sean Middleditch) `_, - `@dixlorenz (Dix Lorenz) `_, - `@CarterLi (李通洲) `_, - `@Naios `_, - `@fmatthew5876 (Matthew Fioravante) `_, - `@LevskiWeng (Levski Weng) `_, - `@rpopescu `_, - `@gabime (Gabi Melman) `_, - `@cubicool (Jeremy Moles) `_, - `@jkflying (Julian Kent) `_, - `@LogicalKnight (Sean L) `_, - `@inguin (Ingo van Lil) `_ and - `@Jopie64 (Johan) `_. - -* Fixed portability issues (mostly causing test failures) on ARM, ppc64, ppc64le, - s390x and SunOS 5.11 i386 - (`#138 `_, - `#179 `_, - `#180 `_, - `#202 `_, - `#225 `_, - `Red Hat Bugzilla Bug 1260297 `_). - Thanks to `@Naios `_, - `@jackyf (Eugene V. Lyubimkin) `_ and Dave Johansen. - -* Fixed a name conflict with macro ``free`` defined in - ``crtdbg.h`` when ``_CRTDBG_MAP_ALLOC`` is set - (`#211 `_). - -* Fixed shared library build on OS X - (`#212 `_). - Thanks to `@dean0x7d (Dean Moldovan) `_. - -* Fixed an overload conflict on MSVC when ``/Zc:wchar_t-`` option is specified - (`#214 `_). - Thanks to `@slavanap (Vyacheslav Napadovsky) `_. - -* Improved compatibility with MSVC 2008 - (`#236 `_). - Thanks to `@Jopie64 (Johan) `_. - -* Improved compatibility with bcc32 - (`#227 `_). - -* Fixed ``static_assert`` detection on Clang - (`#228 `_). - Thanks to `@dean0x7d (Dean Moldovan) `_. - -1.1.0 - 2015-03-06 ------------------- - -* Added ``BasicArrayWriter``, a class template that provides operations for - formatting and writing data into a fixed-size array - (`#105 `_ and - `#122 `_): - - .. code:: c++ - - char buffer[100]; - fmt::ArrayWriter w(buffer); - w.write("The answer is {}", 42); - -* Added `0 A.D. `_ and `PenUltima Online (POL) - `_ to the list of notable projects using C++ Format. - -* C++ Format now uses MSVC intrinsics for better formatting performance - (`#115 `_, - `#116 `_, - `#118 `_ and - `#121 `_). - Previously these optimizations where only used on GCC and Clang. - Thanks to `@CarterLi `_ and - `@objectx `_. - -* CMake install target (`#119 `_). - Thanks to `@TrentHouliston `_. - - You can now install C++ Format with ``make install`` command. - -* Improved `Biicode `_ support - (`#98 `_ and - `#104 `_). Thanks to - `@MariadeAnton `_ and - `@franramirez688 `_. - -* Improved support for building with `Android NDK - `_ - (`#107 `_). - Thanks to `@newnon `_. - - The `android-ndk-example `_ - repository provides and example of using C++ Format with Android NDK: - - .. image:: https://raw.githubusercontent.com/fmtlib/android-ndk-example/ - master/screenshot.png - -* Improved documentation of ``SystemError`` and ``WindowsError`` - (`#54 `_). - -* Various code improvements - (`#110 `_, - `#111 `_ - `#112 `_). - Thanks to `@CarterLi `_. - -* Improved compile-time errors when formatting wide into narrow strings - (`#117 `_). - -* Fixed ``BasicWriter::write`` without formatting arguments when C++11 support - is disabled (`#109 `_). - -* Fixed header-only build on OS X with GCC 4.9 - (`#124 `_). - -* Fixed packaging issues (`#94 `_). - -* Added `changelog `_ - (`#103 `_). - -1.0.0 - 2015-02-05 ------------------- - -* Add support for a header-only configuration when ``FMT_HEADER_ONLY`` is - defined before including ``format.h``: - - .. code:: c++ - - #define FMT_HEADER_ONLY - #include "format.h" - -* Compute string length in the constructor of ``BasicStringRef`` - instead of the ``size`` method - (`#79 `_). - This eliminates size computation for string literals on reasonable optimizing - compilers. - -* Fix formatting of types with overloaded ``operator <<`` for ``std::wostream`` - (`#86 `_): - - .. code:: c++ - - fmt::format(L"The date is {0}", Date(2012, 12, 9)); - -* Fix linkage of tests on Arch Linux - (`#89 `_). - -* Allow precision specifier for non-float arguments - (`#90 `_): - - .. code:: c++ - - fmt::print("{:.3}\n", "Carpet"); // prints "Car" - -* Fix build on Android NDK - (`#93 `_) - -* Improvements to documentation build procedure. - -* Remove ``FMT_SHARED`` CMake variable in favor of standard `BUILD_SHARED_LIBS - `_. - -* Fix error handling in ``fmt::fprintf``. - -* Fix a number of warnings. - -0.12.0 - 2014-10-25 -------------------- - -* [Breaking] Improved separation between formatting and buffer management. - ``Writer`` is now a base class that cannot be instantiated directly. - The new ``MemoryWriter`` class implements the default buffer management - with small allocations done on stack. So ``fmt::Writer`` should be replaced - with ``fmt::MemoryWriter`` in variable declarations. - - Old code: - - .. code:: c++ - - fmt::Writer w; - - New code: - - .. code:: c++ - - fmt::MemoryWriter w; - - If you pass ``fmt::Writer`` by reference, you can continue to do so: - - .. code:: c++ - - void f(fmt::Writer &w); - - This doesn't affect the formatting API. - -* Support for custom memory allocators - (`#69 `_) - -* Formatting functions now accept `signed char` and `unsigned char` strings as - arguments (`#73 `_): - - .. code:: c++ - - auto s = format("GLSL version: {}", glGetString(GL_VERSION)); - -* Reduced code bloat. According to the new `benchmark results - `_, - cppformat is close to ``printf`` and by the order of magnitude better than - Boost Format in terms of compiled code size. - -* Improved appearance of the documentation on mobile by using the `Sphinx - Bootstrap theme `_: - - .. |old| image:: https://cloud.githubusercontent.com/assets/576385/4792130/ - cd256436-5de3-11e4-9a62-c077d0c2b003.png - - .. |new| image:: https://cloud.githubusercontent.com/assets/576385/4792131/ - cd29896c-5de3-11e4-8f59-cac952942bf0.png - - +-------+-------+ - | Old | New | - +-------+-------+ - | |old| | |new| | - +-------+-------+ - -0.11.0 - 2014-08-21 -------------------- - -* Safe printf implementation with a POSIX extension for positional arguments: - - .. code:: c++ - - fmt::printf("Elapsed time: %.2f seconds", 1.23); - fmt::printf("%1$s, %3$d %2$s", weekday, month, day); - -* Arguments of ``char`` type can now be formatted as integers - (Issue `#55 `_): - - .. code:: c++ - - fmt::format("0x{0:02X}", 'a'); - -* Deprecated parts of the API removed. - -* The library is now built and tested on MinGW with Appveyor in addition to - existing test platforms Linux/GCC, OS X/Clang, Windows/MSVC. - -0.10.0 - 2014-07-01 -------------------- - -**Improved API** - -* All formatting methods are now implemented as variadic functions instead - of using ``operator<<`` for feeding arbitrary arguments into a temporary - formatter object. This works both with C++11 where variadic templates are - used and with older standards where variadic functions are emulated by - providing lightweight wrapper functions defined with the ``FMT_VARIADIC`` - macro. You can use this macro for defining your own portable variadic - functions: - - .. code:: c++ - - void report_error(const char *format, const fmt::ArgList &args) { - fmt::print("Error: {}"); - fmt::print(format, args); - } - FMT_VARIADIC(void, report_error, const char *) - - report_error("file not found: {}", path); - - Apart from a more natural syntax, this also improves performance as there - is no need to construct temporary formatter objects and control arguments' - lifetimes. Because the wrapper functions are very lightweight, this doesn't - cause code bloat even in pre-C++11 mode. - -* Simplified common case of formatting an ``std::string``. Now it requires a - single function call: - - .. code:: c++ - - std::string s = format("The answer is {}.", 42); - - Previously it required 2 function calls: - - .. code:: c++ - - std::string s = str(Format("The answer is {}.") << 42); - - Instead of unsafe ``c_str`` function, ``fmt::Writer`` should be used directly - to bypass creation of ``std::string``: - - .. code:: c++ - - fmt::Writer w; - w.write("The answer is {}.", 42); - w.c_str(); // returns a C string - - This doesn't do dynamic memory allocation for small strings and is less error - prone as the lifetime of the string is the same as for ``std::string::c_str`` - which is well understood (hopefully). - -* Improved consistency in naming functions that are a part of the public API. - Now all public functions are lowercase following the standard library - conventions. Previously it was a combination of lowercase and - CapitalizedWords. - Issue `#50 `_. - -* Old functions are marked as deprecated and will be removed in the next - release. - -**Other Changes** - -* Experimental support for printf format specifications (work in progress): - - .. code:: c++ - - fmt::printf("The answer is %d.", 42); - std::string s = fmt::sprintf("Look, a %s!", "string"); - -* Support for hexadecimal floating point format specifiers ``a`` and ``A``: - - .. code:: c++ - - print("{:a}", -42.0); // Prints -0x1.5p+5 - print("{:A}", -42.0); // Prints -0X1.5P+5 - -* CMake option ``FMT_SHARED`` that specifies whether to build format as a - shared library (off by default). - -0.9.0 - 2014-05-13 ------------------- - -* More efficient implementation of variadic formatting functions. - -* ``Writer::Format`` now has a variadic overload: - - .. code:: c++ - - Writer out; - out.Format("Look, I'm {}!", "variadic"); - -* For efficiency and consistency with other overloads, variadic overload of - the ``Format`` function now returns ``Writer`` instead of ``std::string``. - Use the ``str`` function to convert it to ``std::string``: - - .. code:: c++ - - std::string s = str(Format("Look, I'm {}!", "variadic")); - -* Replaced formatter actions with output sinks: ``NoAction`` -> ``NullSink``, - ``Write`` -> ``FileSink``, ``ColorWriter`` -> ``ANSITerminalSink``. - This improves naming consistency and shouldn't affect client code unless - these classes are used directly which should be rarely needed. - -* Added ``ThrowSystemError`` function that formats a message and throws - ``SystemError`` containing the formatted message and system-specific error - description. For example, the following code - - .. code:: c++ - - FILE *f = fopen(filename, "r"); - if (!f) - ThrowSystemError(errno, "Failed to open file '{}'") << filename; - - will throw ``SystemError`` exception with description - "Failed to open file '': No such file or directory" if file - doesn't exist. - -* Support for AppVeyor continuous integration platform. - -* ``Format`` now throws ``SystemError`` in case of I/O errors. - -* Improve test infrastructure. Print functions are now tested by redirecting - the output to a pipe. - -0.8.0 - 2014-04-14 ------------------- - -* Initial release diff --git a/Externals/fmt/LICENSE.rst b/Externals/fmt/LICENSE.rst deleted file mode 100755 index f0ec3db4d2..0000000000 --- a/Externals/fmt/LICENSE.rst +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 - present, Victor Zverovich - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---- Optional exception to the license --- - -As an exception, if, as a result of your compiling your source code, portions -of this Software are embedded into a machine-executable object form of such -source code, you may redistribute such embedded portions in such object form -without including the above copyright and permission notices. diff --git a/Externals/fmt/README.rst b/Externals/fmt/README.rst deleted file mode 100755 index cc6d7c41ad..0000000000 --- a/Externals/fmt/README.rst +++ /dev/null @@ -1,531 +0,0 @@ -.. image:: https://user-images.githubusercontent.com/ - 576385/156254208-f5b743a9-88cf-439d-b0c0-923d53e8d551.png - :width: 25% - :alt: {fmt} - -.. image:: https://github.com/fmtlib/fmt/workflows/linux/badge.svg - :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux - -.. image:: https://github.com/fmtlib/fmt/workflows/macos/badge.svg - :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos - -.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg - :target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows - -.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg - :alt: fmt is continuously fuzzed at oss-fuzz - :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\ - colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\ - Summary&q=proj%3Dfmt&can=1 - -.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg - :alt: Ask questions at StackOverflow with the tag fmt - :target: https://stackoverflow.com/questions/tagged/fmt - -**{fmt}** is an open-source formatting library providing a fast and safe -alternative to C stdio and C++ iostreams. - -If you like this project, please consider donating to one of the funds that -help victims of the war in Ukraine: https://www.stopputin.net/. - -`Documentation `__ - -`Cheat Sheets `__ - -Q&A: ask questions on `StackOverflow with the tag fmt -`_. - -Try {fmt} in `Compiler Explorer `_. - -Features --------- - -* Simple `format API `_ with positional arguments - for localization -* Implementation of `C++20 std::format - `__ -* `Format string syntax `_ similar to Python's - `format `_ -* Fast IEEE 754 floating-point formatter with correct rounding, shortness and - round-trip guarantees -* Safe `printf implementation - `_ including the POSIX - extension for positional arguments -* Extensibility: `support for user-defined types - `_ -* High performance: faster than common standard library implementations of - ``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_ - and `Converting a hundred million integers to strings per second - `_ -* Small code size both in terms of source code with the minimum configuration - consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``, - and compiled code; see `Compile time and code bloat`_ -* Reliability: the library has an extensive set of `tests - `_ and is `continuously fuzzed - `_ -* Safety: the library is fully type safe, errors in format strings can be - reported at compile time, automatic memory management prevents buffer overflow - errors -* Ease of use: small self-contained code base, no external dependencies, - permissive MIT `license - `_ -* `Portability `_ with - consistent output across platforms and support for older compilers -* Clean warning-free codebase even on high warning levels such as - ``-Wall -Wextra -pedantic`` -* Locale-independence by default -* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro - -See the `documentation `_ for more details. - -Examples --------- - -**Print to stdout** (`run `_) - -.. code:: c++ - - #include - - int main() { - fmt::print("Hello, world!\n"); - } - -**Format a string** (`run `_) - -.. code:: c++ - - std::string s = fmt::format("The answer is {}.", 42); - // s == "The answer is 42." - -**Format a string using positional arguments** (`run `_) - -.. code:: c++ - - std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy"); - // s == "I'd rather be happy than right." - -**Print chrono durations** (`run `_) - -.. code:: c++ - - #include - - int main() { - using namespace std::literals::chrono_literals; - fmt::print("Default format: {} {}\n", 42s, 100ms); - fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s); - } - -Output:: - - Default format: 42s 100ms - strftime-like format: 03:15:30 - -**Print a container** (`run `_) - -.. code:: c++ - - #include - #include - - int main() { - std::vector v = {1, 2, 3}; - fmt::print("{}\n", v); - } - -Output:: - - [1, 2, 3] - -**Check a format string at compile time** - -.. code:: c++ - - std::string s = fmt::format("{:d}", "I am not a number"); - -This gives a compile-time error in C++20 because ``d`` is an invalid format -specifier for a string. - -**Write a file from a single thread** - -.. code:: c++ - - #include - - int main() { - auto out = fmt::output_file("guide.txt"); - out.print("Don't {}", "Panic"); - } - -This can be `5 to 9 times faster than fprintf -`_. - -**Print with colors and text styles** - -.. code:: c++ - - #include - - int main() { - fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold, - "Hello, {}!\n", "world"); - fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) | - fmt::emphasis::underline, "Hello, {}!\n", "мир"); - fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic, - "Hello, {}!\n", "世界"); - } - -Output on a modern terminal: - -.. image:: https://user-images.githubusercontent.com/ - 576385/88485597-d312f600-cf2b-11ea-9cbe-61f535a86e28.png - -Benchmarks ----------- - -Speed tests -~~~~~~~~~~~ - -================= ============= =========== -Library Method Run Time, s -================= ============= =========== -libc printf 1.04 -libc++ std::ostream 3.05 -{fmt} 6.1.1 fmt::print 0.75 -Boost Format 1.67 boost::format 7.24 -Folly Format folly::format 2.23 -================= ============= =========== - -{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``. - -The above results were generated by building ``tinyformat_test.cpp`` on macOS -10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the -best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"`` -or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for -further details refer to the `source -`_. - -{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on -floating-point formatting (`dtoa-benchmark `_) -and faster than `double-conversion `_ and -`ryu `_: - -.. image:: https://user-images.githubusercontent.com/576385/ - 95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png - :target: https://fmt.dev/unknown_mac64_clang12.0.html - -Compile time and code bloat -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The script `bloat-test.py -`_ -from `format-benchmark `_ -tests compile time and code bloat for nontrivial projects. -It generates 100 translation units and uses ``printf()`` or its alternative -five times in each to simulate a medium sized project. The resulting -executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42), -macOS Sierra, best of three) is shown in the following tables. - -**Optimized build (-O3)** - -============= =============== ==================== ================== -Method Compile Time, s Executable size, KiB Stripped size, KiB -============= =============== ==================== ================== -printf 2.6 29 26 -printf+string 16.4 29 26 -iostreams 31.1 59 55 -{fmt} 19.0 37 34 -Boost Format 91.9 226 203 -Folly Format 115.7 101 88 -============= =============== ==================== ================== - -As you can see, {fmt} has 60% less overhead in terms of resulting binary code -size compared to iostreams and comes pretty close to ``printf``. Boost Format -and Folly Format have the largest overheads. - -``printf+string`` is the same as ``printf`` but with extra ```` -include to measure the overhead of the latter. - -**Non-optimized build** - -============= =============== ==================== ================== -Method Compile Time, s Executable size, KiB Stripped size, KiB -============= =============== ==================== ================== -printf 2.2 33 30 -printf+string 16.0 33 30 -iostreams 28.3 56 52 -{fmt} 18.2 59 50 -Boost Format 54.1 365 303 -Folly Format 79.9 445 430 -============= =============== ==================== ================== - -``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to -compare formatting function overhead only. Boost Format is a -header-only library so it doesn't provide any linkage options. - -Running the tests -~~~~~~~~~~~~~~~~~ - -Please refer to `Building the library`__ for the instructions on how to build -the library and run the unit tests. - -__ https://fmt.dev/latest/usage.html#building-the-library - -Benchmarks reside in a separate repository, -`format-benchmarks `_, -so to run the benchmarks you first need to clone this repository and -generate Makefiles with CMake:: - - $ git clone --recursive https://github.com/fmtlib/format-benchmark.git - $ cd format-benchmark - $ cmake . - -Then you can run the speed test:: - - $ make speed-test - -or the bloat test:: - - $ make bloat-test - -Migrating code --------------- - -`clang-tidy-fmt `_ provides clang -tidy checks for converting occurrences of ``printf`` and ``fprintf`` to -``fmt::print``. - -Projects using this library ---------------------------- - -* `0 A.D. `_: a free, open-source, cross-platform - real-time strategy game - -* `2GIS `_: free business listings with a city map - -* `AMPL/MP `_: - an open-source library for mathematical programming - -* `Aseprite `_: - animated sprite editor & pixel art tool - -* `AvioBook `_: a comprehensive aircraft - operations suite - -* `Blizzard Battle.net `_: an online gaming platform - -* `Celestia `_: real-time 3D visualization of space - -* `Ceph `_: a scalable distributed storage system - -* `ccache `_: a compiler cache - -* `ClickHouse `_: analytical database - management system - -* `CUAUV `_: Cornell University's autonomous underwater - vehicle - -* `Drake `_: a planning, control, and analysis toolbox - for nonlinear dynamical systems (MIT) - -* `Envoy `_: C++ L7 proxy and communication bus - (Lyft) - -* `FiveM `_: a modification framework for GTA V - -* `fmtlog `_: a performant fmtlib-style - logging library with latency in nanoseconds - -* `Folly `_: Facebook open-source library - -* `GemRB `_: a portable open-source implementation of - Bioware’s Infinity Engine - -* `Grand Mountain Adventure - `_: - a beautiful open-world ski & snowboarding game - -* `HarpyWar/pvpgn `_: - Player vs Player Gaming Network with tweaks - -* `KBEngine `_: an open-source MMOG server - engine - -* `Keypirinha `_: a semantic launcher for Windows - -* `Kodi `_ (formerly xbmc): home theater software - -* `Knuth `_: high-performance Bitcoin full-node - -* `Microsoft Verona `_: - research programming language for concurrent ownership - -* `MongoDB `_: distributed document database - -* `MongoDB Smasher `_: a small tool to - generate randomized datasets - -* `OpenSpace `_: an open-source - astrovisualization framework - -* `PenUltima Online (POL) `_: - an MMO server, compatible with most Ultima Online clients - -* `PyTorch `_: an open-source machine - learning library - -* `quasardb `_: a distributed, high-performance, - associative database - -* `Quill `_: asynchronous low-latency logging library - -* `QKW `_: generalizing aliasing to simplify - navigation, and executing complex multi-line terminal command sequences - -* `redis-cerberus `_: a Redis cluster - proxy - -* `redpanda `_: a 10x faster Kafka® replacement - for mission critical systems written in C++ - -* `rpclib `_: a modern C++ msgpack-RPC server and client - library - -* `Salesforce Analytics Cloud - `_: - business intelligence software - -* `Scylla `_: a Cassandra-compatible NoSQL data store - that can handle 1 million transactions per second on a single server - -* `Seastar `_: an advanced, open-source C++ - framework for high-performance server applications on modern hardware - -* `spdlog `_: super fast C++ logging library - -* `Stellar `_: financial platform - -* `Touch Surgery `_: surgery simulator - -* `TrinityCore `_: open-source - MMORPG framework - -* `Windows Terminal `_: the new Windows - terminal - -`More... `_ - -If you are aware of other projects using this library, please let me know -by `email `_ or by submitting an -`issue `_. - -Motivation ----------- - -So why yet another formatting library? - -There are plenty of methods for doing this task, from standard ones like -the printf family of function and iostreams to Boost Format and FastFormat -libraries. The reason for creating a new library is that every existing -solution that I found either had serious issues or didn't provide -all the features I needed. - -printf -~~~~~~ - -The good thing about ``printf`` is that it is pretty fast and readily available -being a part of the C standard library. The main drawback is that it -doesn't support user-defined types. ``printf`` also has safety issues although -they are somewhat mitigated with `__attribute__ ((format (printf, ...)) -`_ in GCC. -There is a POSIX extension that adds positional arguments required for -`i18n `_ -to ``printf`` but it is not a part of C99 and may not be available on some -platforms. - -iostreams -~~~~~~~~~ - -The main issue with iostreams is best illustrated with an example: - -.. code:: c++ - - std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n"; - -which is a lot of typing compared to printf: - -.. code:: c++ - - printf("%.2f\n", 1.23456); - -Matthew Wilson, the author of FastFormat, called this "chevron hell". iostreams -don't support positional arguments by design. - -The good part is that iostreams support user-defined types and are safe although -error handling is awkward. - -Boost Format -~~~~~~~~~~~~ - -This is a very powerful library which supports both ``printf``-like format -strings and positional arguments. Its main drawback is performance. According to -various benchmarks, it is much slower than other methods considered here. Boost -Format also has excessive build times and severe code bloat issues (see -`Benchmarks`_). - -FastFormat -~~~~~~~~~~ - -This is an interesting library which is fast, safe and has positional arguments. -However, it has significant limitations, citing its author: - - Three features that have no hope of being accommodated within the - current design are: - - * Leading zeros (or any other non-space padding) - * Octal/hexadecimal encoding - * Runtime width/alignment specification - -It is also quite big and has a heavy dependency, STLSoft, which might be too -restrictive for using it in some projects. - -Boost Spirit.Karma -~~~~~~~~~~~~~~~~~~ - -This is not really a formatting library but I decided to include it here for -completeness. As iostreams, it suffers from the problem of mixing verbatim text -with arguments. The library is pretty fast, but slower on integer formatting -than ``fmt::format_to`` with format string compilation on Karma's own benchmark, -see `Converting a hundred million integers to strings per second -`_. - -License -------- - -{fmt} is distributed under the MIT `license -`_. - -Documentation License ---------------------- - -The `Format String Syntax `_ -section in the documentation is based on the one from Python `string module -documentation `_. -For this reason the documentation is distributed under the Python Software -Foundation license available in `doc/python-license.txt -`_. -It only applies if you distribute the documentation of {fmt}. - -Maintainers ------------ - -The {fmt} library is maintained by Victor Zverovich (`vitaut -`_) and Jonathan Müller (`foonathan -`_) with contributions from many other people. -See `Contributors `_ and -`Releases `_ for some of the names. -Let us know if your contribution is not listed or mentioned incorrectly and -we'll make it right. diff --git a/Externals/fmt/exports.props b/Externals/fmt/exports.props index d2bad14164..25e07f5e17 100644 --- a/Externals/fmt/exports.props +++ b/Externals/fmt/exports.props @@ -2,7 +2,7 @@ - $(ExternalsDir)fmt\include;%(AdditionalIncludeDirectories) + $(ExternalsDir)fmt\fmt\include;%(AdditionalIncludeDirectories) diff --git a/Externals/fmt/fmt b/Externals/fmt/fmt new file mode 160000 index 0000000000..f5e54359df --- /dev/null +++ b/Externals/fmt/fmt @@ -0,0 +1 @@ +Subproject commit f5e54359df4c26b6230fc61d38aa294581393084 diff --git a/Externals/fmt/fmt.vcxproj b/Externals/fmt/fmt.vcxproj index b7015f0558..9a978b5848 100644 --- a/Externals/fmt/fmt.vcxproj +++ b/Externals/fmt/fmt.vcxproj @@ -17,27 +17,27 @@ - include;%(AdditionalIncludeDirectories) + fmt\include;%(AdditionalIncludeDirectories) - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/Externals/fmt/include/fmt/args.h b/Externals/fmt/include/fmt/args.h deleted file mode 100644 index a3966d1407..0000000000 --- a/Externals/fmt/include/fmt/args.h +++ /dev/null @@ -1,234 +0,0 @@ -// Formatting library for C++ - dynamic format arguments -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_ARGS_H_ -#define FMT_ARGS_H_ - -#include // std::reference_wrapper -#include // std::unique_ptr -#include - -#include "core.h" - -FMT_BEGIN_NAMESPACE - -namespace detail { - -template struct is_reference_wrapper : std::false_type {}; -template -struct is_reference_wrapper> : std::true_type {}; - -template const T& unwrap(const T& v) { return v; } -template const T& unwrap(const std::reference_wrapper& v) { - return static_cast(v); -} - -class dynamic_arg_list { - // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for - // templates it doesn't complain about inability to deduce single translation - // unit for placing vtable. So storage_node_base is made a fake template. - template struct node { - virtual ~node() = default; - std::unique_ptr> next; - }; - - template struct typed_node : node<> { - T value; - - template - FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} - - template - FMT_CONSTEXPR typed_node(const basic_string_view& arg) - : value(arg.data(), arg.size()) {} - }; - - std::unique_ptr> head_; - - public: - template const T& push(const Arg& arg) { - auto new_node = std::unique_ptr>(new typed_node(arg)); - auto& value = new_node->value; - new_node->next = std::move(head_); - head_ = std::move(new_node); - return value; - } -}; -} // namespace detail - -/** - \rst - A dynamic version of `fmt::format_arg_store`. - It's equipped with a storage to potentially temporary objects which lifetimes - could be shorter than the format arguments object. - - It can be implicitly converted into `~fmt::basic_format_args` for passing - into type-erased formatting functions such as `~fmt::vformat`. - \endrst - */ -template -class dynamic_format_arg_store -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args -#endif -{ - private: - using char_type = typename Context::char_type; - - template struct need_copy { - static constexpr detail::type mapped_type = - detail::mapped_type_constant::value; - - enum { - value = !(detail::is_reference_wrapper::value || - std::is_same>::value || - std::is_same>::value || - (mapped_type != detail::type::cstring_type && - mapped_type != detail::type::string_type && - mapped_type != detail::type::custom_type)) - }; - }; - - template - using stored_type = conditional_t< - std::is_convertible>::value && - !detail::is_reference_wrapper::value, - std::basic_string, T>; - - // Storage of basic_format_arg must be contiguous. - std::vector> data_; - std::vector> named_info_; - - // Storage of arguments not fitting into basic_format_arg must grow - // without relocation because items in data_ refer to it. - detail::dynamic_arg_list dynamic_args_; - - friend class basic_format_args; - - unsigned long long get_types() const { - return detail::is_unpacked_bit | data_.size() | - (named_info_.empty() - ? 0ULL - : static_cast(detail::has_named_args_bit)); - } - - const basic_format_arg* data() const { - return named_info_.empty() ? data_.data() : data_.data() + 1; - } - - template void emplace_arg(const T& arg) { - data_.emplace_back(detail::make_arg(arg)); - } - - template - void emplace_arg(const detail::named_arg& arg) { - if (named_info_.empty()) { - constexpr const detail::named_arg_info* zero_ptr{nullptr}; - data_.insert(data_.begin(), {zero_ptr, 0}); - } - data_.emplace_back(detail::make_arg(detail::unwrap(arg.value))); - auto pop_one = [](std::vector>* data) { - data->pop_back(); - }; - std::unique_ptr>, decltype(pop_one)> - guard{&data_, pop_one}; - named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); - data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; - guard.release(); - } - - public: - constexpr dynamic_format_arg_store() = default; - - /** - \rst - Adds an argument into the dynamic store for later passing to a formatting - function. - - Note that custom types and string types (but not string views) are copied - into the store dynamically allocating memory if necessary. - - **Example**:: - - fmt::dynamic_format_arg_store store; - store.push_back(42); - store.push_back("abc"); - store.push_back(1.5f); - std::string result = fmt::vformat("{} and {} and {}", store); - \endrst - */ - template void push_back(const T& arg) { - if (detail::const_check(need_copy::value)) - emplace_arg(dynamic_args_.push>(arg)); - else - emplace_arg(detail::unwrap(arg)); - } - - /** - \rst - Adds a reference to the argument into the dynamic store for later passing to - a formatting function. - - **Example**:: - - fmt::dynamic_format_arg_store store; - char band[] = "Rolling Stones"; - store.push_back(std::cref(band)); - band[9] = 'c'; // Changing str affects the output. - std::string result = fmt::vformat("{}", store); - // result == "Rolling Scones" - \endrst - */ - template void push_back(std::reference_wrapper arg) { - static_assert( - need_copy::value, - "objects of built-in types and string views are always copied"); - emplace_arg(arg.get()); - } - - /** - Adds named argument into the dynamic store for later passing to a formatting - function. ``std::reference_wrapper`` is supported to avoid copying of the - argument. The name is always copied into the store. - */ - template - void push_back(const detail::named_arg& arg) { - const char_type* arg_name = - dynamic_args_.push>(arg.name).c_str(); - if (detail::const_check(need_copy::value)) { - emplace_arg( - fmt::arg(arg_name, dynamic_args_.push>(arg.value))); - } else { - emplace_arg(fmt::arg(arg_name, arg.value)); - } - } - - /** Erase all elements from the store */ - void clear() { - data_.clear(); - named_info_.clear(); - dynamic_args_ = detail::dynamic_arg_list(); - } - - /** - \rst - Reserves space to store at least *new_cap* arguments including - *new_cap_named* named arguments. - \endrst - */ - void reserve(size_t new_cap, size_t new_cap_named) { - FMT_ASSERT(new_cap >= new_cap_named, - "Set of arguments includes set of named arguments"); - data_.reserve(new_cap); - named_info_.reserve(new_cap_named); - } -}; - -FMT_END_NAMESPACE - -#endif // FMT_ARGS_H_ diff --git a/Externals/fmt/include/fmt/chrono.h b/Externals/fmt/include/fmt/chrono.h deleted file mode 100755 index b112f76e99..0000000000 --- a/Externals/fmt/include/fmt/chrono.h +++ /dev/null @@ -1,2069 +0,0 @@ -// Formatting library for C++ - chrono support -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_CHRONO_H_ -#define FMT_CHRONO_H_ - -#include -#include -#include // std::isfinite -#include // std::memcpy -#include -#include -#include -#include -#include - -#include "format.h" - -FMT_BEGIN_NAMESPACE - -// Enable tzset. -#ifndef FMT_USE_TZSET -// UWP doesn't provide _tzset. -# if FMT_HAS_INCLUDE("winapifamily.h") -# include -# endif -# if defined(_WIN32) && (!defined(WINAPI_FAMILY) || \ - (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) -# define FMT_USE_TZSET 1 -# else -# define FMT_USE_TZSET 0 -# endif -#endif - -// Enable safe chrono durations, unless explicitly disabled. -#ifndef FMT_SAFE_DURATION_CAST -# define FMT_SAFE_DURATION_CAST 1 -#endif -#if FMT_SAFE_DURATION_CAST - -// For conversion between std::chrono::durations without undefined -// behaviour or erroneous results. -// This is a stripped down version of duration_cast, for inclusion in fmt. -// See https://github.com/pauldreik/safe_duration_cast -// -// Copyright Paul Dreik 2019 -namespace safe_duration_cast { - -template ::value && - std::numeric_limits::is_signed == - std::numeric_limits::is_signed)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { - ec = 0; - using F = std::numeric_limits; - using T = std::numeric_limits; - static_assert(F::is_integer, "From must be integral"); - static_assert(T::is_integer, "To must be integral"); - - // A and B are both signed, or both unsigned. - if (detail::const_check(F::digits <= T::digits)) { - // From fits in To without any problem. - } else { - // From does not always fit in To, resort to a dynamic check. - if (from < (T::min)() || from > (T::max)()) { - // outside range. - ec = 1; - return {}; - } - } - return static_cast(from); -} - -/** - * converts From to To, without loss. If the dynamic value of from - * can't be converted to To without loss, ec is set. - */ -template ::value && - std::numeric_limits::is_signed != - std::numeric_limits::is_signed)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { - ec = 0; - using F = std::numeric_limits; - using T = std::numeric_limits; - static_assert(F::is_integer, "From must be integral"); - static_assert(T::is_integer, "To must be integral"); - - if (detail::const_check(F::is_signed && !T::is_signed)) { - // From may be negative, not allowed! - if (fmt::detail::is_negative(from)) { - ec = 1; - return {}; - } - // From is positive. Can it always fit in To? - if (detail::const_check(F::digits > T::digits) && - from > static_cast(detail::max_value())) { - ec = 1; - return {}; - } - } - - if (detail::const_check(!F::is_signed && T::is_signed && - F::digits >= T::digits) && - from > static_cast(detail::max_value())) { - ec = 1; - return {}; - } - return static_cast(from); // Lossless conversion. -} - -template ::value)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { - ec = 0; - return from; -} // function - -// clang-format off -/** - * converts From to To if possible, otherwise ec is set. - * - * input | output - * ---------------------------------|--------------- - * NaN | NaN - * Inf | Inf - * normal, fits in output | converted (possibly lossy) - * normal, does not fit in output | ec is set - * subnormal | best effort - * -Inf | -Inf - */ -// clang-format on -template ::value)> -FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { - ec = 0; - using T = std::numeric_limits; - static_assert(std::is_floating_point::value, "From must be floating"); - static_assert(std::is_floating_point::value, "To must be floating"); - - // catch the only happy case - if (std::isfinite(from)) { - if (from >= T::lowest() && from <= (T::max)()) { - return static_cast(from); - } - // not within range. - ec = 1; - return {}; - } - - // nan and inf will be preserved - return static_cast(from); -} // function - -template ::value)> -FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { - ec = 0; - static_assert(std::is_floating_point::value, "From must be floating"); - return from; -} - -/** - * safe duration cast between integral durations - */ -template ::value), - FMT_ENABLE_IF(std::is_integral::value)> -To safe_duration_cast(std::chrono::duration from, - int& ec) { - using From = std::chrono::duration; - ec = 0; - // the basic idea is that we need to convert from count() in the from type - // to count() in the To type, by multiplying it with this: - struct Factor - : std::ratio_divide {}; - - static_assert(Factor::num > 0, "num must be positive"); - static_assert(Factor::den > 0, "den must be positive"); - - // the conversion is like this: multiply from.count() with Factor::num - // /Factor::den and convert it to To::rep, all this without - // overflow/underflow. let's start by finding a suitable type that can hold - // both To, From and Factor::num - using IntermediateRep = - typename std::common_type::type; - - // safe conversion to IntermediateRep - IntermediateRep count = - lossless_integral_conversion(from.count(), ec); - if (ec) return {}; - // multiply with Factor::num without overflow or underflow - if (detail::const_check(Factor::num != 1)) { - const auto max1 = detail::max_value() / Factor::num; - if (count > max1) { - ec = 1; - return {}; - } - const auto min1 = - (std::numeric_limits::min)() / Factor::num; - if (!std::is_unsigned::value && count < min1) { - ec = 1; - return {}; - } - count *= Factor::num; - } - - if (detail::const_check(Factor::den != 1)) count /= Factor::den; - auto tocount = lossless_integral_conversion(count, ec); - return ec ? To() : To(tocount); -} - -/** - * safe duration_cast between floating point durations - */ -template ::value), - FMT_ENABLE_IF(std::is_floating_point::value)> -To safe_duration_cast(std::chrono::duration from, - int& ec) { - using From = std::chrono::duration; - ec = 0; - if (std::isnan(from.count())) { - // nan in, gives nan out. easy. - return To{std::numeric_limits::quiet_NaN()}; - } - // maybe we should also check if from is denormal, and decide what to do about - // it. - - // +-inf should be preserved. - if (std::isinf(from.count())) { - return To{from.count()}; - } - - // the basic idea is that we need to convert from count() in the from type - // to count() in the To type, by multiplying it with this: - struct Factor - : std::ratio_divide {}; - - static_assert(Factor::num > 0, "num must be positive"); - static_assert(Factor::den > 0, "den must be positive"); - - // the conversion is like this: multiply from.count() with Factor::num - // /Factor::den and convert it to To::rep, all this without - // overflow/underflow. let's start by finding a suitable type that can hold - // both To, From and Factor::num - using IntermediateRep = - typename std::common_type::type; - - // force conversion of From::rep -> IntermediateRep to be safe, - // even if it will never happen be narrowing in this context. - IntermediateRep count = - safe_float_conversion(from.count(), ec); - if (ec) { - return {}; - } - - // multiply with Factor::num without overflow or underflow - if (detail::const_check(Factor::num != 1)) { - constexpr auto max1 = detail::max_value() / - static_cast(Factor::num); - if (count > max1) { - ec = 1; - return {}; - } - constexpr auto min1 = std::numeric_limits::lowest() / - static_cast(Factor::num); - if (count < min1) { - ec = 1; - return {}; - } - count *= static_cast(Factor::num); - } - - // this can't go wrong, right? den>0 is checked earlier. - if (detail::const_check(Factor::den != 1)) { - using common_t = typename std::common_type::type; - count /= static_cast(Factor::den); - } - - // convert to the to type, safely - using ToRep = typename To::rep; - - const ToRep tocount = safe_float_conversion(count, ec); - if (ec) { - return {}; - } - return To{tocount}; -} -} // namespace safe_duration_cast -#endif - -// Prevents expansion of a preceding token as a function-style macro. -// Usage: f FMT_NOMACRO() -#define FMT_NOMACRO - -namespace detail { -template struct null {}; -inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } -inline null<> localtime_s(...) { return null<>(); } -inline null<> gmtime_r(...) { return null<>(); } -inline null<> gmtime_s(...) { return null<>(); } - -inline const std::locale& get_classic_locale() { - static const auto& locale = std::locale::classic(); - return locale; -} - -template struct codecvt_result { - static constexpr const size_t max_size = 32; - CodeUnit buf[max_size]; - CodeUnit* end; -}; -template -constexpr const size_t codecvt_result::max_size; - -template -void write_codecvt(codecvt_result& out, string_view in_buf, - const std::locale& loc) { -#if FMT_CLANG_VERSION -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated" - auto& f = std::use_facet>(loc); -# pragma clang diagnostic pop -#else - auto& f = std::use_facet>(loc); -#endif - auto mb = std::mbstate_t(); - const char* from_next = nullptr; - auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next, - std::begin(out.buf), std::end(out.buf), out.end); - if (result != std::codecvt_base::ok) - FMT_THROW(format_error("failed to format time")); -} - -template -auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) - -> OutputIt { - if (detail::is_utf8() && loc != get_classic_locale()) { - // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and - // gcc-4. -#if FMT_MSC_VERSION != 0 || \ - (defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI)) - // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 - // and newer. - using code_unit = wchar_t; -#else - using code_unit = char32_t; -#endif - - using unit_t = codecvt_result; - unit_t unit; - write_codecvt(unit, in, loc); - // In UTF-8 is used one to four one-byte code units. - auto&& buf = basic_memory_buffer(); - for (code_unit* p = unit.buf; p != unit.end; ++p) { - uint32_t c = static_cast(*p); - if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) { - // surrogate pair - ++p; - if (p == unit.end || (c & 0xfc00) != 0xd800 || - (*p & 0xfc00) != 0xdc00) { - FMT_THROW(format_error("failed to format time")); - } - c = (c << 10) + static_cast(*p) - 0x35fdc00; - } - if (c < 0x80) { - buf.push_back(static_cast(c)); - } else if (c < 0x800) { - buf.push_back(static_cast(0xc0 | (c >> 6))); - buf.push_back(static_cast(0x80 | (c & 0x3f))); - } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { - buf.push_back(static_cast(0xe0 | (c >> 12))); - buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - buf.push_back(static_cast(0x80 | (c & 0x3f))); - } else if (c >= 0x10000 && c <= 0x10ffff) { - buf.push_back(static_cast(0xf0 | (c >> 18))); - buf.push_back(static_cast(0x80 | ((c & 0x3ffff) >> 12))); - buf.push_back(static_cast(0x80 | ((c & 0xfff) >> 6))); - buf.push_back(static_cast(0x80 | (c & 0x3f))); - } else { - FMT_THROW(format_error("failed to format time")); - } - } - return copy_str(buf.data(), buf.data() + buf.size(), out); - } - return copy_str(in.data(), in.data() + in.size(), out); -} - -template ::value)> -auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) - -> OutputIt { - codecvt_result unit; - write_codecvt(unit, sv, loc); - return copy_str(unit.buf, unit.end, out); -} - -template ::value)> -auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc) - -> OutputIt { - return write_encoded_tm_str(out, sv, loc); -} - -template -inline void do_write(buffer& buf, const std::tm& time, - const std::locale& loc, char format, char modifier) { - auto&& format_buf = formatbuf>(buf); - auto&& os = std::basic_ostream(&format_buf); - os.imbue(loc); - using iterator = std::ostreambuf_iterator; - const auto& facet = std::use_facet>(loc); - auto end = facet.put(os, os, Char(' '), &time, format, modifier); - if (end.failed()) FMT_THROW(format_error("failed to format time")); -} - -template ::value)> -auto write(OutputIt out, const std::tm& time, const std::locale& loc, - char format, char modifier = 0) -> OutputIt { - auto&& buf = get_buffer(out); - do_write(buf, time, loc, format, modifier); - return buf.out(); -} - -template ::value)> -auto write(OutputIt out, const std::tm& time, const std::locale& loc, - char format, char modifier = 0) -> OutputIt { - auto&& buf = basic_memory_buffer(); - do_write(buf, time, loc, format, modifier); - return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); -} - -} // namespace detail - -FMT_MODULE_EXPORT_BEGIN - -/** - Converts given time since epoch as ``std::time_t`` value into calendar time, - expressed in local time. Unlike ``std::localtime``, this function is - thread-safe on most platforms. - */ -inline std::tm localtime(std::time_t time) { - struct dispatcher { - std::time_t time_; - std::tm tm_; - - dispatcher(std::time_t t) : time_(t) {} - - bool run() { - using namespace fmt::detail; - return handle(localtime_r(&time_, &tm_)); - } - - bool handle(std::tm* tm) { return tm != nullptr; } - - bool handle(detail::null<>) { - using namespace fmt::detail; - return fallback(localtime_s(&tm_, &time_)); - } - - bool fallback(int res) { return res == 0; } - -#if !FMT_MSC_VERSION - bool fallback(detail::null<>) { - using namespace fmt::detail; - std::tm* tm = std::localtime(&time_); - if (tm) tm_ = *tm; - return tm != nullptr; - } -#endif - }; - dispatcher lt(time); - // Too big time values may be unsupported. - if (!lt.run()) FMT_THROW(format_error("time_t value out of range")); - return lt.tm_; -} - -inline std::tm localtime( - std::chrono::time_point time_point) { - return localtime(std::chrono::system_clock::to_time_t(time_point)); -} - -/** - Converts given time since epoch as ``std::time_t`` value into calendar time, - expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this - function is thread-safe on most platforms. - */ -inline std::tm gmtime(std::time_t time) { - struct dispatcher { - std::time_t time_; - std::tm tm_; - - dispatcher(std::time_t t) : time_(t) {} - - bool run() { - using namespace fmt::detail; - return handle(gmtime_r(&time_, &tm_)); - } - - bool handle(std::tm* tm) { return tm != nullptr; } - - bool handle(detail::null<>) { - using namespace fmt::detail; - return fallback(gmtime_s(&tm_, &time_)); - } - - bool fallback(int res) { return res == 0; } - -#if !FMT_MSC_VERSION - bool fallback(detail::null<>) { - std::tm* tm = std::gmtime(&time_); - if (tm) tm_ = *tm; - return tm != nullptr; - } -#endif - }; - dispatcher gt(time); - // Too big time values may be unsupported. - if (!gt.run()) FMT_THROW(format_error("time_t value out of range")); - return gt.tm_; -} - -inline std::tm gmtime( - std::chrono::time_point time_point) { - return gmtime(std::chrono::system_clock::to_time_t(time_point)); -} - -FMT_BEGIN_DETAIL_NAMESPACE - -// Writes two-digit numbers a, b and c separated by sep to buf. -// The method by Pavel Novikov based on -// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/. -inline void write_digit2_separated(char* buf, unsigned a, unsigned b, - unsigned c, char sep) { - unsigned long long digits = - a | (b << 24) | (static_cast(c) << 48); - // Convert each value to BCD. - // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b. - // The difference is - // y - x = a * 6 - // a can be found from x: - // a = floor(x / 10) - // then - // y = x + a * 6 = x + floor(x / 10) * 6 - // floor(x / 10) is (x * 205) >> 11 (needs 16 bits). - digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6; - // Put low nibbles to high bytes and high nibbles to low bytes. - digits = ((digits & 0x00f00000f00000f0) >> 4) | - ((digits & 0x000f00000f00000f) << 8); - auto usep = static_cast(sep); - // Add ASCII '0' to each digit byte and insert separators. - digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); - - constexpr const size_t len = 8; - if (const_check(is_big_endian())) { - char tmp[len]; - std::memcpy(tmp, &digits, len); - std::reverse_copy(tmp, tmp + len, buf); - } else { - std::memcpy(buf, &digits, len); - } -} - -template FMT_CONSTEXPR inline const char* get_units() { - if (std::is_same::value) return "as"; - if (std::is_same::value) return "fs"; - if (std::is_same::value) return "ps"; - if (std::is_same::value) return "ns"; - if (std::is_same::value) return "µs"; - if (std::is_same::value) return "ms"; - if (std::is_same::value) return "cs"; - if (std::is_same::value) return "ds"; - if (std::is_same>::value) return "s"; - if (std::is_same::value) return "das"; - if (std::is_same::value) return "hs"; - if (std::is_same::value) return "ks"; - if (std::is_same::value) return "Ms"; - if (std::is_same::value) return "Gs"; - if (std::is_same::value) return "Ts"; - if (std::is_same::value) return "Ps"; - if (std::is_same::value) return "Es"; - if (std::is_same>::value) return "m"; - if (std::is_same>::value) return "h"; - return nullptr; -} - -enum class numeric_system { - standard, - // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale. - alternative -}; - -// Parses a put_time-like format string and invokes handler actions. -template -FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, - const Char* end, - Handler&& handler) { - auto ptr = begin; - while (ptr != end) { - auto c = *ptr; - if (c == '}') break; - if (c != '%') { - ++ptr; - continue; - } - if (begin != ptr) handler.on_text(begin, ptr); - ++ptr; // consume '%' - if (ptr == end) FMT_THROW(format_error("invalid format")); - c = *ptr++; - switch (c) { - case '%': - handler.on_text(ptr - 1, ptr); - break; - case 'n': { - const Char newline[] = {'\n'}; - handler.on_text(newline, newline + 1); - break; - } - case 't': { - const Char tab[] = {'\t'}; - handler.on_text(tab, tab + 1); - break; - } - // Year: - case 'Y': - handler.on_year(numeric_system::standard); - break; - case 'y': - handler.on_short_year(numeric_system::standard); - break; - case 'C': - handler.on_century(numeric_system::standard); - break; - case 'G': - handler.on_iso_week_based_year(); - break; - case 'g': - handler.on_iso_week_based_short_year(); - break; - // Day of the week: - case 'a': - handler.on_abbr_weekday(); - break; - case 'A': - handler.on_full_weekday(); - break; - case 'w': - handler.on_dec0_weekday(numeric_system::standard); - break; - case 'u': - handler.on_dec1_weekday(numeric_system::standard); - break; - // Month: - case 'b': - case 'h': - handler.on_abbr_month(); - break; - case 'B': - handler.on_full_month(); - break; - case 'm': - handler.on_dec_month(numeric_system::standard); - break; - // Day of the year/month: - case 'U': - handler.on_dec0_week_of_year(numeric_system::standard); - break; - case 'W': - handler.on_dec1_week_of_year(numeric_system::standard); - break; - case 'V': - handler.on_iso_week_of_year(numeric_system::standard); - break; - case 'j': - handler.on_day_of_year(); - break; - case 'd': - handler.on_day_of_month(numeric_system::standard); - break; - case 'e': - handler.on_day_of_month_space(numeric_system::standard); - break; - // Hour, minute, second: - case 'H': - handler.on_24_hour(numeric_system::standard); - break; - case 'I': - handler.on_12_hour(numeric_system::standard); - break; - case 'M': - handler.on_minute(numeric_system::standard); - break; - case 'S': - handler.on_second(numeric_system::standard); - break; - // Other: - case 'c': - handler.on_datetime(numeric_system::standard); - break; - case 'x': - handler.on_loc_date(numeric_system::standard); - break; - case 'X': - handler.on_loc_time(numeric_system::standard); - break; - case 'D': - handler.on_us_date(); - break; - case 'F': - handler.on_iso_date(); - break; - case 'r': - handler.on_12_hour_time(); - break; - case 'R': - handler.on_24_hour_time(); - break; - case 'T': - handler.on_iso_time(); - break; - case 'p': - handler.on_am_pm(); - break; - case 'Q': - handler.on_duration_value(); - break; - case 'q': - handler.on_duration_unit(); - break; - case 'z': - handler.on_utc_offset(); - break; - case 'Z': - handler.on_tz_name(); - break; - // Alternative representation: - case 'E': { - if (ptr == end) FMT_THROW(format_error("invalid format")); - c = *ptr++; - switch (c) { - case 'Y': - handler.on_year(numeric_system::alternative); - break; - case 'y': - handler.on_offset_year(); - break; - case 'C': - handler.on_century(numeric_system::alternative); - break; - case 'c': - handler.on_datetime(numeric_system::alternative); - break; - case 'x': - handler.on_loc_date(numeric_system::alternative); - break; - case 'X': - handler.on_loc_time(numeric_system::alternative); - break; - default: - FMT_THROW(format_error("invalid format")); - } - break; - } - case 'O': - if (ptr == end) FMT_THROW(format_error("invalid format")); - c = *ptr++; - switch (c) { - case 'y': - handler.on_short_year(numeric_system::alternative); - break; - case 'm': - handler.on_dec_month(numeric_system::alternative); - break; - case 'U': - handler.on_dec0_week_of_year(numeric_system::alternative); - break; - case 'W': - handler.on_dec1_week_of_year(numeric_system::alternative); - break; - case 'V': - handler.on_iso_week_of_year(numeric_system::alternative); - break; - case 'd': - handler.on_day_of_month(numeric_system::alternative); - break; - case 'e': - handler.on_day_of_month_space(numeric_system::alternative); - break; - case 'w': - handler.on_dec0_weekday(numeric_system::alternative); - break; - case 'u': - handler.on_dec1_weekday(numeric_system::alternative); - break; - case 'H': - handler.on_24_hour(numeric_system::alternative); - break; - case 'I': - handler.on_12_hour(numeric_system::alternative); - break; - case 'M': - handler.on_minute(numeric_system::alternative); - break; - case 'S': - handler.on_second(numeric_system::alternative); - break; - default: - FMT_THROW(format_error("invalid format")); - } - break; - default: - FMT_THROW(format_error("invalid format")); - } - begin = ptr; - } - if (begin != ptr) handler.on_text(begin, ptr); - return ptr; -} - -template struct null_chrono_spec_handler { - FMT_CONSTEXPR void unsupported() { - static_cast(this)->unsupported(); - } - FMT_CONSTEXPR void on_year(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_offset_year() { unsupported(); } - FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); } - FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); } - FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); } - FMT_CONSTEXPR void on_full_weekday() { unsupported(); } - FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_abbr_month() { unsupported(); } - FMT_CONSTEXPR void on_full_month() { unsupported(); } - FMT_CONSTEXPR void on_dec_month(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_day_of_year() { unsupported(); } - FMT_CONSTEXPR void on_day_of_month(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_day_of_month_space(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_us_date() { unsupported(); } - FMT_CONSTEXPR void on_iso_date() { unsupported(); } - FMT_CONSTEXPR void on_12_hour_time() { unsupported(); } - FMT_CONSTEXPR void on_24_hour_time() { unsupported(); } - FMT_CONSTEXPR void on_iso_time() { unsupported(); } - FMT_CONSTEXPR void on_am_pm() { unsupported(); } - FMT_CONSTEXPR void on_duration_value() { unsupported(); } - FMT_CONSTEXPR void on_duration_unit() { unsupported(); } - FMT_CONSTEXPR void on_utc_offset() { unsupported(); } - FMT_CONSTEXPR void on_tz_name() { unsupported(); } -}; - -struct tm_format_checker : null_chrono_spec_handler { - FMT_NORETURN void unsupported() { FMT_THROW(format_error("no format")); } - - template - FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - FMT_CONSTEXPR void on_year(numeric_system) {} - FMT_CONSTEXPR void on_short_year(numeric_system) {} - FMT_CONSTEXPR void on_offset_year() {} - FMT_CONSTEXPR void on_century(numeric_system) {} - FMT_CONSTEXPR void on_iso_week_based_year() {} - FMT_CONSTEXPR void on_iso_week_based_short_year() {} - FMT_CONSTEXPR void on_abbr_weekday() {} - FMT_CONSTEXPR void on_full_weekday() {} - FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {} - FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {} - FMT_CONSTEXPR void on_abbr_month() {} - FMT_CONSTEXPR void on_full_month() {} - FMT_CONSTEXPR void on_dec_month(numeric_system) {} - FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) {} - FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) {} - FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) {} - FMT_CONSTEXPR void on_day_of_year() {} - FMT_CONSTEXPR void on_day_of_month(numeric_system) {} - FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {} - FMT_CONSTEXPR void on_24_hour(numeric_system) {} - FMT_CONSTEXPR void on_12_hour(numeric_system) {} - FMT_CONSTEXPR void on_minute(numeric_system) {} - FMT_CONSTEXPR void on_second(numeric_system) {} - FMT_CONSTEXPR void on_datetime(numeric_system) {} - FMT_CONSTEXPR void on_loc_date(numeric_system) {} - FMT_CONSTEXPR void on_loc_time(numeric_system) {} - FMT_CONSTEXPR void on_us_date() {} - FMT_CONSTEXPR void on_iso_date() {} - FMT_CONSTEXPR void on_12_hour_time() {} - FMT_CONSTEXPR void on_24_hour_time() {} - FMT_CONSTEXPR void on_iso_time() {} - FMT_CONSTEXPR void on_am_pm() {} - FMT_CONSTEXPR void on_utc_offset() {} - FMT_CONSTEXPR void on_tz_name() {} -}; - -inline const char* tm_wday_full_name(int wday) { - static constexpr const char* full_name_list[] = { - "Sunday", "Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday"}; - return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; -} -inline const char* tm_wday_short_name(int wday) { - static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", - "Thu", "Fri", "Sat"}; - return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; -} - -inline const char* tm_mon_full_name(int mon) { - static constexpr const char* full_name_list[] = { - "January", "February", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December"}; - return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; -} -inline const char* tm_mon_short_name(int mon) { - static constexpr const char* short_name_list[] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - }; - return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???"; -} - -template -struct has_member_data_tm_gmtoff : std::false_type {}; -template -struct has_member_data_tm_gmtoff> - : std::true_type {}; - -template -struct has_member_data_tm_zone : std::false_type {}; -template -struct has_member_data_tm_zone> - : std::true_type {}; - -#if FMT_USE_TZSET -inline void tzset_once() { - static bool init = []() -> bool { - _tzset(); - return true; - }(); - ignore_unused(init); -} -#endif - -template class tm_writer { - private: - static constexpr int days_per_week = 7; - - const std::locale& loc_; - const bool is_classic_; - OutputIt out_; - const std::tm& tm_; - - auto tm_sec() const noexcept -> int { - FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, ""); - return tm_.tm_sec; - } - auto tm_min() const noexcept -> int { - FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, ""); - return tm_.tm_min; - } - auto tm_hour() const noexcept -> int { - FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, ""); - return tm_.tm_hour; - } - auto tm_mday() const noexcept -> int { - FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, ""); - return tm_.tm_mday; - } - auto tm_mon() const noexcept -> int { - FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, ""); - return tm_.tm_mon; - } - auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; } - auto tm_wday() const noexcept -> int { - FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, ""); - return tm_.tm_wday; - } - auto tm_yday() const noexcept -> int { - FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, ""); - return tm_.tm_yday; - } - - auto tm_hour12() const noexcept -> int { - const auto h = tm_hour(); - const auto z = h < 12 ? h : h - 12; - return z == 0 ? 12 : z; - } - - // POSIX and the C Standard are unclear or inconsistent about what %C and %y - // do if the year is negative or exceeds 9999. Use the convention that %C - // concatenated with %y yields the same output as %Y, and that %Y contains at - // least 4 characters, with more only if necessary. - auto split_year_lower(long long year) const noexcept -> int { - auto l = year % 100; - if (l < 0) l = -l; // l in [0, 99] - return static_cast(l); - } - - // Algorithm: - // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_from_a_month_and_day_of_the_month_or_ordinal_date - auto iso_year_weeks(long long curr_year) const noexcept -> int { - const auto prev_year = curr_year - 1; - const auto curr_p = - (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % - days_per_week; - const auto prev_p = - (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % - days_per_week; - return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); - } - auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int { - return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) / - days_per_week; - } - auto tm_iso_week_year() const noexcept -> long long { - const auto year = tm_year(); - const auto w = iso_week_num(tm_yday(), tm_wday()); - if (w < 1) return year - 1; - if (w > iso_year_weeks(year)) return year + 1; - return year; - } - auto tm_iso_week_of_year() const noexcept -> int { - const auto year = tm_year(); - const auto w = iso_week_num(tm_yday(), tm_wday()); - if (w < 1) return iso_year_weeks(year - 1); - if (w > iso_year_weeks(year)) return 1; - return w; - } - - void write1(int value) { - *out_++ = static_cast('0' + to_unsigned(value) % 10); - } - void write2(int value) { - const char* d = digits2(to_unsigned(value) % 100); - *out_++ = *d++; - *out_++ = *d; - } - - void write_year_extended(long long year) { - // At least 4 characters. - int width = 4; - if (year < 0) { - *out_++ = '-'; - year = 0 - year; - --width; - } - uint32_or_64_or_128_t n = to_unsigned(year); - const int num_digits = count_digits(n); - if (width > num_digits) out_ = std::fill_n(out_, width - num_digits, '0'); - out_ = format_decimal(out_, n, num_digits).end; - } - void write_year(long long year) { - if (year >= 0 && year < 10000) { - write2(static_cast(year / 100)); - write2(static_cast(year % 100)); - } else { - write_year_extended(year); - } - } - - void write_utc_offset(long offset) { - if (offset < 0) { - *out_++ = '-'; - offset = -offset; - } else { - *out_++ = '+'; - } - offset /= 60; - write2(static_cast(offset / 60)); - write2(static_cast(offset % 60)); - } - template ::value)> - void format_utc_offset_impl(const T& tm) { - write_utc_offset(tm.tm_gmtoff); - } - template ::value)> - void format_utc_offset_impl(const T& tm) { -#if defined(_WIN32) && defined(_UCRT) -# if FMT_USE_TZSET - tzset_once(); -# endif - long offset = 0; - _get_timezone(&offset); - if (tm.tm_isdst) { - long dstbias = 0; - _get_dstbias(&dstbias); - offset += dstbias; - } - write_utc_offset(-offset); -#else - ignore_unused(tm); - format_localized('z'); -#endif - } - - template ::value)> - void format_tz_name_impl(const T& tm) { - if (is_classic_) - out_ = write_tm_str(out_, tm.tm_zone, loc_); - else - format_localized('Z'); - } - template ::value)> - void format_tz_name_impl(const T&) { - format_localized('Z'); - } - - void format_localized(char format, char modifier = 0) { - out_ = write(out_, tm_, loc_, format, modifier); - } - - public: - tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm) - : loc_(loc), - is_classic_(loc_ == get_classic_locale()), - out_(out), - tm_(tm) {} - - OutputIt out() const { return out_; } - - FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { - out_ = copy_str(begin, end, out_); - } - - void on_abbr_weekday() { - if (is_classic_) - out_ = write(out_, tm_wday_short_name(tm_wday())); - else - format_localized('a'); - } - void on_full_weekday() { - if (is_classic_) - out_ = write(out_, tm_wday_full_name(tm_wday())); - else - format_localized('A'); - } - void on_dec0_weekday(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday()); - format_localized('w', 'O'); - } - void on_dec1_weekday(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) { - auto wday = tm_wday(); - write1(wday == 0 ? days_per_week : wday); - } else { - format_localized('u', 'O'); - } - } - - void on_abbr_month() { - if (is_classic_) - out_ = write(out_, tm_mon_short_name(tm_mon())); - else - format_localized('b'); - } - void on_full_month() { - if (is_classic_) - out_ = write(out_, tm_mon_full_name(tm_mon())); - else - format_localized('B'); - } - - void on_datetime(numeric_system ns) { - if (is_classic_) { - on_abbr_weekday(); - *out_++ = ' '; - on_abbr_month(); - *out_++ = ' '; - on_day_of_month_space(numeric_system::standard); - *out_++ = ' '; - on_iso_time(); - *out_++ = ' '; - on_year(numeric_system::standard); - } else { - format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); - } - } - void on_loc_date(numeric_system ns) { - if (is_classic_) - on_us_date(); - else - format_localized('x', ns == numeric_system::standard ? '\0' : 'E'); - } - void on_loc_time(numeric_system ns) { - if (is_classic_) - on_iso_time(); - else - format_localized('X', ns == numeric_system::standard ? '\0' : 'E'); - } - void on_us_date() { - char buf[8]; - write_digit2_separated(buf, to_unsigned(tm_mon() + 1), - to_unsigned(tm_mday()), - to_unsigned(split_year_lower(tm_year())), '/'); - out_ = copy_str(std::begin(buf), std::end(buf), out_); - } - void on_iso_date() { - auto year = tm_year(); - char buf[10]; - size_t offset = 0; - if (year >= 0 && year < 10000) { - copy2(buf, digits2(static_cast(year / 100))); - } else { - offset = 4; - write_year_extended(year); - year = 0; - } - write_digit2_separated(buf + 2, static_cast(year % 100), - to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), - '-'); - out_ = copy_str(std::begin(buf) + offset, std::end(buf), out_); - } - - void on_utc_offset() { format_utc_offset_impl(tm_); } - void on_tz_name() { format_tz_name_impl(tm_); } - - void on_year(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) - return write_year(tm_year()); - format_localized('Y', 'E'); - } - void on_short_year(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) - return write2(split_year_lower(tm_year())); - format_localized('y', 'O'); - } - void on_offset_year() { - if (is_classic_) return write2(split_year_lower(tm_year())); - format_localized('y', 'E'); - } - - void on_century(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) { - auto year = tm_year(); - auto upper = year / 100; - if (year >= -99 && year < 0) { - // Zero upper on negative year. - *out_++ = '-'; - *out_++ = '0'; - } else if (upper >= 0 && upper < 100) { - write2(static_cast(upper)); - } else { - out_ = write(out_, upper); - } - } else { - format_localized('C', 'E'); - } - } - - void on_dec_month(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) - return write2(tm_mon() + 1); - format_localized('m', 'O'); - } - - void on_dec0_week_of_year(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) - return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week); - format_localized('U', 'O'); - } - void on_dec1_week_of_year(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) { - auto wday = tm_wday(); - write2((tm_yday() + days_per_week - - (wday == 0 ? (days_per_week - 1) : (wday - 1))) / - days_per_week); - } else { - format_localized('W', 'O'); - } - } - void on_iso_week_of_year(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) - return write2(tm_iso_week_of_year()); - format_localized('V', 'O'); - } - - void on_iso_week_based_year() { write_year(tm_iso_week_year()); } - void on_iso_week_based_short_year() { - write2(split_year_lower(tm_iso_week_year())); - } - - void on_day_of_year() { - auto yday = tm_yday() + 1; - write1(yday / 100); - write2(yday % 100); - } - void on_day_of_month(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_mday()); - format_localized('d', 'O'); - } - void on_day_of_month_space(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) { - auto mday = to_unsigned(tm_mday()) % 100; - const char* d2 = digits2(mday); - *out_++ = mday < 10 ? ' ' : d2[0]; - *out_++ = d2[1]; - } else { - format_localized('e', 'O'); - } - } - - void on_24_hour(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_hour()); - format_localized('H', 'O'); - } - void on_12_hour(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) - return write2(tm_hour12()); - format_localized('I', 'O'); - } - void on_minute(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_min()); - format_localized('M', 'O'); - } - void on_second(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_sec()); - format_localized('S', 'O'); - } - - void on_12_hour_time() { - if (is_classic_) { - char buf[8]; - write_digit2_separated(buf, to_unsigned(tm_hour12()), - to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); - out_ = copy_str(std::begin(buf), std::end(buf), out_); - *out_++ = ' '; - on_am_pm(); - } else { - format_localized('r'); - } - } - void on_24_hour_time() { - write2(tm_hour()); - *out_++ = ':'; - write2(tm_min()); - } - void on_iso_time() { - char buf[8]; - write_digit2_separated(buf, to_unsigned(tm_hour()), to_unsigned(tm_min()), - to_unsigned(tm_sec()), ':'); - out_ = copy_str(std::begin(buf), std::end(buf), out_); - } - - void on_am_pm() { - if (is_classic_) { - *out_++ = tm_hour() < 12 ? 'A' : 'P'; - *out_++ = 'M'; - } else { - format_localized('p'); - } - } - - // These apply to chrono durations but not tm. - void on_duration_value() {} - void on_duration_unit() {} -}; - -struct chrono_format_checker : null_chrono_spec_handler { - FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } - - template - FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - FMT_CONSTEXPR void on_24_hour(numeric_system) {} - FMT_CONSTEXPR void on_12_hour(numeric_system) {} - FMT_CONSTEXPR void on_minute(numeric_system) {} - FMT_CONSTEXPR void on_second(numeric_system) {} - FMT_CONSTEXPR void on_12_hour_time() {} - FMT_CONSTEXPR void on_24_hour_time() {} - FMT_CONSTEXPR void on_iso_time() {} - FMT_CONSTEXPR void on_am_pm() {} - FMT_CONSTEXPR void on_duration_value() {} - FMT_CONSTEXPR void on_duration_unit() {} -}; - -template ::value)> -inline bool isfinite(T) { - return true; -} - -// Converts value to Int and checks that it's in the range [0, upper). -template ::value)> -inline Int to_nonnegative_int(T value, Int upper) { - FMT_ASSERT(std::is_unsigned::value || - (value >= 0 && to_unsigned(value) <= to_unsigned(upper)), - "invalid value"); - (void)upper; - return static_cast(value); -} -template ::value)> -inline Int to_nonnegative_int(T value, Int upper) { - if (value < 0 || value > static_cast(upper)) - FMT_THROW(format_error("invalid value")); - return static_cast(value); -} - -template ::value)> -inline T mod(T x, int y) { - return x % static_cast(y); -} -template ::value)> -inline T mod(T x, int y) { - return std::fmod(x, static_cast(y)); -} - -// If T is an integral type, maps T to its unsigned counterpart, otherwise -// leaves it unchanged (unlike std::make_unsigned). -template ::value> -struct make_unsigned_or_unchanged { - using type = T; -}; - -template struct make_unsigned_or_unchanged { - using type = typename std::make_unsigned::type; -}; - -#if FMT_SAFE_DURATION_CAST -// throwing version of safe_duration_cast -template -To fmt_safe_duration_cast(std::chrono::duration from) { - int ec; - To to = safe_duration_cast::safe_duration_cast(from, ec); - if (ec) FMT_THROW(format_error("cannot format duration")); - return to; -} -#endif - -template ::value)> -inline std::chrono::duration get_milliseconds( - std::chrono::duration d) { - // this may overflow and/or the result may not fit in the - // target type. -#if FMT_SAFE_DURATION_CAST - using CommonSecondsType = - typename std::common_type::type; - const auto d_as_common = fmt_safe_duration_cast(d); - const auto d_as_whole_seconds = - fmt_safe_duration_cast(d_as_common); - // this conversion should be nonproblematic - const auto diff = d_as_common - d_as_whole_seconds; - const auto ms = - fmt_safe_duration_cast>(diff); - return ms; -#else - auto s = std::chrono::duration_cast(d); - return std::chrono::duration_cast(d - s); -#endif -} - -// Counts the number of fractional digits in the range [0, 18] according to the -// C++20 spec. If more than 18 fractional digits are required then returns 6 for -// microseconds precision. -template () / 10)> -struct count_fractional_digits { - static constexpr int value = - Num % Den == 0 ? N : count_fractional_digits::value; -}; - -// Base case that doesn't instantiate any more templates -// in order to avoid overflow. -template -struct count_fractional_digits { - static constexpr int value = (Num % Den == 0) ? N : 6; -}; - -constexpr long long pow10(std::uint32_t n) { - return n == 0 ? 1 : 10 * pow10(n - 1); -} - -template ::is_signed)> -constexpr std::chrono::duration abs( - std::chrono::duration d) { - // We need to compare the duration using the count() method directly - // due to a compiler bug in clang-11 regarding the spaceship operator, - // when -Wzero-as-null-pointer-constant is enabled. - // In clang-12 the bug has been fixed. See - // https://bugs.llvm.org/show_bug.cgi?id=46235 and the reproducible example: - // https://www.godbolt.org/z/Knbb5joYx. - return d.count() >= d.zero().count() ? d : -d; -} - -template ::is_signed)> -constexpr std::chrono::duration abs( - std::chrono::duration d) { - return d; -} - -template ::value)> -OutputIt format_duration_value(OutputIt out, Rep val, int) { - return write(out, val); -} - -template ::value)> -OutputIt format_duration_value(OutputIt out, Rep val, int precision) { - auto specs = basic_format_specs(); - specs.precision = precision; - specs.type = precision >= 0 ? presentation_type::fixed_lower - : presentation_type::general_lower; - return write(out, val, specs); -} - -template -OutputIt copy_unit(string_view unit, OutputIt out, Char) { - return std::copy(unit.begin(), unit.end(), out); -} - -template -OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { - // This works when wchar_t is UTF-32 because units only contain characters - // that have the same representation in UTF-16 and UTF-32. - utf8_to_utf16 u(unit); - return std::copy(u.c_str(), u.c_str() + u.size(), out); -} - -template -OutputIt format_duration_unit(OutputIt out) { - if (const char* unit = get_units()) - return copy_unit(string_view(unit), out, Char()); - *out++ = '['; - out = write(out, Period::num); - if (const_check(Period::den != 1)) { - *out++ = '/'; - out = write(out, Period::den); - } - *out++ = ']'; - *out++ = 's'; - return out; -} - -class get_locale { - private: - union { - std::locale locale_; - }; - bool has_locale_ = false; - - public: - get_locale(bool localized, locale_ref loc) : has_locale_(localized) { - if (localized) - ::new (&locale_) std::locale(loc.template get()); - } - ~get_locale() { - if (has_locale_) locale_.~locale(); - } - operator const std::locale&() const { - return has_locale_ ? locale_ : get_classic_locale(); - } -}; - -template -struct chrono_formatter { - FormatContext& context; - OutputIt out; - int precision; - bool localized = false; - // rep is unsigned to avoid overflow. - using rep = - conditional_t::value && sizeof(Rep) < sizeof(int), - unsigned, typename make_unsigned_or_unchanged::type>; - rep val; - using seconds = std::chrono::duration; - seconds s; - using milliseconds = std::chrono::duration; - bool negative; - - using char_type = typename FormatContext::char_type; - using tm_writer_type = tm_writer; - - chrono_formatter(FormatContext& ctx, OutputIt o, - std::chrono::duration d) - : context(ctx), - out(o), - val(static_cast(d.count())), - negative(false) { - if (d.count() < 0) { - val = 0 - val; - negative = true; - } - - // this may overflow and/or the result may not fit in the - // target type. -#if FMT_SAFE_DURATION_CAST - // might need checked conversion (rep!=Rep) - auto tmpval = std::chrono::duration(val); - s = fmt_safe_duration_cast(tmpval); -#else - s = std::chrono::duration_cast( - std::chrono::duration(val)); -#endif - } - - // returns true if nan or inf, writes to out. - bool handle_nan_inf() { - if (isfinite(val)) { - return false; - } - if (isnan(val)) { - write_nan(); - return true; - } - // must be +-inf - if (val > 0) { - write_pinf(); - } else { - write_ninf(); - } - return true; - } - - Rep hour() const { return static_cast(mod((s.count() / 3600), 24)); } - - Rep hour12() const { - Rep hour = static_cast(mod((s.count() / 3600), 12)); - return hour <= 0 ? 12 : hour; - } - - Rep minute() const { return static_cast(mod((s.count() / 60), 60)); } - Rep second() const { return static_cast(mod(s.count(), 60)); } - - std::tm time() const { - auto time = std::tm(); - time.tm_hour = to_nonnegative_int(hour(), 24); - time.tm_min = to_nonnegative_int(minute(), 60); - time.tm_sec = to_nonnegative_int(second(), 60); - return time; - } - - void write_sign() { - if (negative) { - *out++ = '-'; - negative = false; - } - } - - void write(Rep value, int width) { - write_sign(); - if (isnan(value)) return write_nan(); - uint32_or_64_or_128_t n = - to_unsigned(to_nonnegative_int(value, max_value())); - int num_digits = detail::count_digits(n); - if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); - out = format_decimal(out, n, num_digits).end; - } - - template void write_fractional_seconds(Duration d) { - FMT_ASSERT(!std::is_floating_point::value, ""); - constexpr auto num_fractional_digits = - count_fractional_digits::value; - - using subsecond_precision = std::chrono::duration< - typename std::common_type::type, - std::ratio<1, detail::pow10(num_fractional_digits)>>; - if (std::ratio_less::value) { - *out++ = '.'; - auto fractional = - detail::abs(d) - std::chrono::duration_cast(d); - auto subseconds = - std::chrono::treat_as_floating_point< - typename subsecond_precision::rep>::value - ? fractional.count() - : std::chrono::duration_cast(fractional) - .count(); - uint32_or_64_or_128_t n = - to_unsigned(to_nonnegative_int(subseconds, max_value())); - int num_digits = detail::count_digits(n); - if (num_fractional_digits > num_digits) - out = std::fill_n(out, num_fractional_digits - num_digits, '0'); - out = format_decimal(out, n, num_digits).end; - } - } - - void write_nan() { std::copy_n("nan", 3, out); } - void write_pinf() { std::copy_n("inf", 3, out); } - void write_ninf() { std::copy_n("-inf", 4, out); } - - template - void format_tm(const tm& time, Callback cb, Args... args) { - if (isnan(val)) return write_nan(); - get_locale loc(localized, context.locale()); - auto w = tm_writer_type(loc, out, time); - (w.*cb)(args...); - out = w.out(); - } - - void on_text(const char_type* begin, const char_type* end) { - std::copy(begin, end, out); - } - - // These are not implemented because durations don't have date information. - void on_abbr_weekday() {} - void on_full_weekday() {} - void on_dec0_weekday(numeric_system) {} - void on_dec1_weekday(numeric_system) {} - void on_abbr_month() {} - void on_full_month() {} - void on_datetime(numeric_system) {} - void on_loc_date(numeric_system) {} - void on_loc_time(numeric_system) {} - void on_us_date() {} - void on_iso_date() {} - void on_utc_offset() {} - void on_tz_name() {} - void on_year(numeric_system) {} - void on_short_year(numeric_system) {} - void on_offset_year() {} - void on_century(numeric_system) {} - void on_iso_week_based_year() {} - void on_iso_week_based_short_year() {} - void on_dec_month(numeric_system) {} - void on_dec0_week_of_year(numeric_system) {} - void on_dec1_week_of_year(numeric_system) {} - void on_iso_week_of_year(numeric_system) {} - void on_day_of_year() {} - void on_day_of_month(numeric_system) {} - void on_day_of_month_space(numeric_system) {} - - void on_24_hour(numeric_system ns) { - if (handle_nan_inf()) return; - - if (ns == numeric_system::standard) return write(hour(), 2); - auto time = tm(); - time.tm_hour = to_nonnegative_int(hour(), 24); - format_tm(time, &tm_writer_type::on_24_hour, ns); - } - - void on_12_hour(numeric_system ns) { - if (handle_nan_inf()) return; - - if (ns == numeric_system::standard) return write(hour12(), 2); - auto time = tm(); - time.tm_hour = to_nonnegative_int(hour12(), 12); - format_tm(time, &tm_writer_type::on_12_hour, ns); - } - - void on_minute(numeric_system ns) { - if (handle_nan_inf()) return; - - if (ns == numeric_system::standard) return write(minute(), 2); - auto time = tm(); - time.tm_min = to_nonnegative_int(minute(), 60); - format_tm(time, &tm_writer_type::on_minute, ns); - } - - void on_second(numeric_system ns) { - if (handle_nan_inf()) return; - - if (ns == numeric_system::standard) { - if (std::is_floating_point::value) { - constexpr auto num_fractional_digits = - count_fractional_digits::value; - auto buf = memory_buffer(); - format_to(std::back_inserter(buf), runtime("{:.{}f}"), - std::fmod(val * static_cast(Period::num) / - static_cast(Period::den), - static_cast(60)), - num_fractional_digits); - if (negative) *out++ = '-'; - if (buf.size() < 2 || buf[1] == '.') *out++ = '0'; - out = std::copy(buf.begin(), buf.end(), out); - } else { - write(second(), 2); - write_fractional_seconds(std::chrono::duration(val)); - } - return; - } - auto time = tm(); - time.tm_sec = to_nonnegative_int(second(), 60); - format_tm(time, &tm_writer_type::on_second, ns); - } - - void on_12_hour_time() { - if (handle_nan_inf()) return; - format_tm(time(), &tm_writer_type::on_12_hour_time); - } - - void on_24_hour_time() { - if (handle_nan_inf()) { - *out++ = ':'; - handle_nan_inf(); - return; - } - - write(hour(), 2); - *out++ = ':'; - write(minute(), 2); - } - - void on_iso_time() { - on_24_hour_time(); - *out++ = ':'; - if (handle_nan_inf()) return; - on_second(numeric_system::standard); - } - - void on_am_pm() { - if (handle_nan_inf()) return; - format_tm(time(), &tm_writer_type::on_am_pm); - } - - void on_duration_value() { - if (handle_nan_inf()) return; - write_sign(); - out = format_duration_value(out, val, precision); - } - - void on_duration_unit() { - out = format_duration_unit(out); - } -}; - -FMT_END_DETAIL_NAMESPACE - -#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907 -using weekday = std::chrono::weekday; -#else -// A fallback version of weekday. -class weekday { - private: - unsigned char value; - - public: - weekday() = default; - explicit constexpr weekday(unsigned wd) noexcept - : value(static_cast(wd != 7 ? wd : 0)) {} - constexpr unsigned c_encoding() const noexcept { return value; } -}; - -class year_month_day {}; -#endif - -// A rudimentary weekday formatter. -template struct formatter { - private: - bool localized = false; - - public: - FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) - -> decltype(ctx.begin()) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin != end && *begin == 'L') { - ++begin; - localized = true; - } - return begin; - } - - template - auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { - auto time = std::tm(); - time.tm_wday = static_cast(wd.c_encoding()); - detail::get_locale loc(localized, ctx.locale()); - auto w = detail::tm_writer(loc, ctx.out(), time); - w.on_abbr_weekday(); - return w.out(); - } -}; - -template -struct formatter, Char> { - private: - basic_format_specs specs; - int precision = -1; - using arg_ref_type = detail::arg_ref; - arg_ref_type width_ref; - arg_ref_type precision_ref; - bool localized = false; - basic_string_view format_str; - using duration = std::chrono::duration; - - struct spec_handler { - formatter& f; - basic_format_parse_context& context; - basic_string_view format_str; - - template FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { - context.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view arg_id) { - context.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) { - return arg_ref_type(context.next_arg_id()); - } - - void on_error(const char* msg) { FMT_THROW(format_error(msg)); } - FMT_CONSTEXPR void on_fill(basic_string_view fill) { - f.specs.fill = fill; - } - FMT_CONSTEXPR void on_align(align_t align) { f.specs.align = align; } - FMT_CONSTEXPR void on_width(int width) { f.specs.width = width; } - FMT_CONSTEXPR void on_precision(int _precision) { - f.precision = _precision; - } - FMT_CONSTEXPR void end_precision() {} - - template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - f.width_ref = make_arg_ref(arg_id); - } - - template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - f.precision_ref = make_arg_ref(arg_id); - } - }; - - using iterator = typename basic_format_parse_context::iterator; - struct parse_range { - iterator begin; - iterator end; - }; - - FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context& ctx) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin == end || *begin == '}') return {begin, begin}; - spec_handler handler{*this, ctx, format_str}; - begin = detail::parse_align(begin, end, handler); - if (begin == end) return {begin, begin}; - begin = detail::parse_width(begin, end, handler); - if (begin == end) return {begin, begin}; - if (*begin == '.') { - if (std::is_floating_point::value) - begin = detail::parse_precision(begin, end, handler); - else - handler.on_error("precision not allowed for this argument type"); - } - if (begin != end && *begin == 'L') { - ++begin; - localized = true; - } - end = detail::parse_chrono_format(begin, end, - detail::chrono_format_checker()); - return {begin, end}; - } - - public: - FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) - -> decltype(ctx.begin()) { - auto range = do_parse(ctx); - format_str = basic_string_view( - &*range.begin, detail::to_unsigned(range.end - range.begin)); - return range.end; - } - - template - auto format(const duration& d, FormatContext& ctx) const - -> decltype(ctx.out()) { - auto specs_copy = specs; - auto precision_copy = precision; - auto begin = format_str.begin(), end = format_str.end(); - // As a possible future optimization, we could avoid extra copying if width - // is not specified. - basic_memory_buffer buf; - auto out = std::back_inserter(buf); - detail::handle_dynamic_spec(specs_copy.width, - width_ref, ctx); - detail::handle_dynamic_spec(precision_copy, - precision_ref, ctx); - if (begin == end || *begin == '}') { - out = detail::format_duration_value(out, d.count(), precision_copy); - detail::format_duration_unit(out); - } else { - detail::chrono_formatter f( - ctx, out, d); - f.precision = precision_copy; - f.localized = localized; - detail::parse_chrono_format(begin, end, f); - } - return detail::write( - ctx.out(), basic_string_view(buf.data(), buf.size()), specs_copy); - } -}; - -template -struct formatter, - Char> : formatter { - FMT_CONSTEXPR formatter() { - basic_string_view default_specs = - detail::string_literal{}; - this->do_parse(default_specs.begin(), default_specs.end()); - } - - template - auto format(std::chrono::time_point val, - FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter::format(localtime(val), ctx); - } -}; - -template struct formatter { - private: - enum class spec { - unknown, - year_month_day, - hh_mm_ss, - }; - spec spec_ = spec::unknown; - basic_string_view specs; - - protected: - template FMT_CONSTEXPR auto do_parse(It begin, It end) -> It { - if (begin != end && *begin == ':') ++begin; - end = detail::parse_chrono_format(begin, end, detail::tm_format_checker()); - // Replace default spec only if the new spec is not empty. - if (end != begin) specs = {begin, detail::to_unsigned(end - begin)}; - return end; - } - - public: - FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) - -> decltype(ctx.begin()) { - auto end = this->do_parse(ctx.begin(), ctx.end()); - // basic_string_view<>::compare isn't constexpr before C++17. - if (specs.size() == 2 && specs[0] == Char('%')) { - if (specs[1] == Char('F')) - spec_ = spec::year_month_day; - else if (specs[1] == Char('T')) - spec_ = spec::hh_mm_ss; - } - return end; - } - - template - auto format(const std::tm& tm, FormatContext& ctx) const - -> decltype(ctx.out()) { - const auto loc_ref = ctx.locale(); - detail::get_locale loc(static_cast(loc_ref), loc_ref); - auto w = detail::tm_writer(loc, ctx.out(), tm); - if (spec_ == spec::year_month_day) - w.on_iso_date(); - else if (spec_ == spec::hh_mm_ss) - w.on_iso_time(); - else - detail::parse_chrono_format(specs.begin(), specs.end(), w); - return w.out(); - } -}; - -FMT_MODULE_EXPORT_END -FMT_END_NAMESPACE - -#endif // FMT_CHRONO_H_ diff --git a/Externals/fmt/include/fmt/color.h b/Externals/fmt/include/fmt/color.h deleted file mode 100755 index 4c163277ef..0000000000 --- a/Externals/fmt/include/fmt/color.h +++ /dev/null @@ -1,651 +0,0 @@ -// Formatting library for C++ - color support -// -// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_COLOR_H_ -#define FMT_COLOR_H_ - -#include "format.h" - -FMT_BEGIN_NAMESPACE -FMT_MODULE_EXPORT_BEGIN - -enum class color : uint32_t { - alice_blue = 0xF0F8FF, // rgb(240,248,255) - antique_white = 0xFAEBD7, // rgb(250,235,215) - aqua = 0x00FFFF, // rgb(0,255,255) - aquamarine = 0x7FFFD4, // rgb(127,255,212) - azure = 0xF0FFFF, // rgb(240,255,255) - beige = 0xF5F5DC, // rgb(245,245,220) - bisque = 0xFFE4C4, // rgb(255,228,196) - black = 0x000000, // rgb(0,0,0) - blanched_almond = 0xFFEBCD, // rgb(255,235,205) - blue = 0x0000FF, // rgb(0,0,255) - blue_violet = 0x8A2BE2, // rgb(138,43,226) - brown = 0xA52A2A, // rgb(165,42,42) - burly_wood = 0xDEB887, // rgb(222,184,135) - cadet_blue = 0x5F9EA0, // rgb(95,158,160) - chartreuse = 0x7FFF00, // rgb(127,255,0) - chocolate = 0xD2691E, // rgb(210,105,30) - coral = 0xFF7F50, // rgb(255,127,80) - cornflower_blue = 0x6495ED, // rgb(100,149,237) - cornsilk = 0xFFF8DC, // rgb(255,248,220) - crimson = 0xDC143C, // rgb(220,20,60) - cyan = 0x00FFFF, // rgb(0,255,255) - dark_blue = 0x00008B, // rgb(0,0,139) - dark_cyan = 0x008B8B, // rgb(0,139,139) - dark_golden_rod = 0xB8860B, // rgb(184,134,11) - dark_gray = 0xA9A9A9, // rgb(169,169,169) - dark_green = 0x006400, // rgb(0,100,0) - dark_khaki = 0xBDB76B, // rgb(189,183,107) - dark_magenta = 0x8B008B, // rgb(139,0,139) - dark_olive_green = 0x556B2F, // rgb(85,107,47) - dark_orange = 0xFF8C00, // rgb(255,140,0) - dark_orchid = 0x9932CC, // rgb(153,50,204) - dark_red = 0x8B0000, // rgb(139,0,0) - dark_salmon = 0xE9967A, // rgb(233,150,122) - dark_sea_green = 0x8FBC8F, // rgb(143,188,143) - dark_slate_blue = 0x483D8B, // rgb(72,61,139) - dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) - dark_turquoise = 0x00CED1, // rgb(0,206,209) - dark_violet = 0x9400D3, // rgb(148,0,211) - deep_pink = 0xFF1493, // rgb(255,20,147) - deep_sky_blue = 0x00BFFF, // rgb(0,191,255) - dim_gray = 0x696969, // rgb(105,105,105) - dodger_blue = 0x1E90FF, // rgb(30,144,255) - fire_brick = 0xB22222, // rgb(178,34,34) - floral_white = 0xFFFAF0, // rgb(255,250,240) - forest_green = 0x228B22, // rgb(34,139,34) - fuchsia = 0xFF00FF, // rgb(255,0,255) - gainsboro = 0xDCDCDC, // rgb(220,220,220) - ghost_white = 0xF8F8FF, // rgb(248,248,255) - gold = 0xFFD700, // rgb(255,215,0) - golden_rod = 0xDAA520, // rgb(218,165,32) - gray = 0x808080, // rgb(128,128,128) - green = 0x008000, // rgb(0,128,0) - green_yellow = 0xADFF2F, // rgb(173,255,47) - honey_dew = 0xF0FFF0, // rgb(240,255,240) - hot_pink = 0xFF69B4, // rgb(255,105,180) - indian_red = 0xCD5C5C, // rgb(205,92,92) - indigo = 0x4B0082, // rgb(75,0,130) - ivory = 0xFFFFF0, // rgb(255,255,240) - khaki = 0xF0E68C, // rgb(240,230,140) - lavender = 0xE6E6FA, // rgb(230,230,250) - lavender_blush = 0xFFF0F5, // rgb(255,240,245) - lawn_green = 0x7CFC00, // rgb(124,252,0) - lemon_chiffon = 0xFFFACD, // rgb(255,250,205) - light_blue = 0xADD8E6, // rgb(173,216,230) - light_coral = 0xF08080, // rgb(240,128,128) - light_cyan = 0xE0FFFF, // rgb(224,255,255) - light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) - light_gray = 0xD3D3D3, // rgb(211,211,211) - light_green = 0x90EE90, // rgb(144,238,144) - light_pink = 0xFFB6C1, // rgb(255,182,193) - light_salmon = 0xFFA07A, // rgb(255,160,122) - light_sea_green = 0x20B2AA, // rgb(32,178,170) - light_sky_blue = 0x87CEFA, // rgb(135,206,250) - light_slate_gray = 0x778899, // rgb(119,136,153) - light_steel_blue = 0xB0C4DE, // rgb(176,196,222) - light_yellow = 0xFFFFE0, // rgb(255,255,224) - lime = 0x00FF00, // rgb(0,255,0) - lime_green = 0x32CD32, // rgb(50,205,50) - linen = 0xFAF0E6, // rgb(250,240,230) - magenta = 0xFF00FF, // rgb(255,0,255) - maroon = 0x800000, // rgb(128,0,0) - medium_aquamarine = 0x66CDAA, // rgb(102,205,170) - medium_blue = 0x0000CD, // rgb(0,0,205) - medium_orchid = 0xBA55D3, // rgb(186,85,211) - medium_purple = 0x9370DB, // rgb(147,112,219) - medium_sea_green = 0x3CB371, // rgb(60,179,113) - medium_slate_blue = 0x7B68EE, // rgb(123,104,238) - medium_spring_green = 0x00FA9A, // rgb(0,250,154) - medium_turquoise = 0x48D1CC, // rgb(72,209,204) - medium_violet_red = 0xC71585, // rgb(199,21,133) - midnight_blue = 0x191970, // rgb(25,25,112) - mint_cream = 0xF5FFFA, // rgb(245,255,250) - misty_rose = 0xFFE4E1, // rgb(255,228,225) - moccasin = 0xFFE4B5, // rgb(255,228,181) - navajo_white = 0xFFDEAD, // rgb(255,222,173) - navy = 0x000080, // rgb(0,0,128) - old_lace = 0xFDF5E6, // rgb(253,245,230) - olive = 0x808000, // rgb(128,128,0) - olive_drab = 0x6B8E23, // rgb(107,142,35) - orange = 0xFFA500, // rgb(255,165,0) - orange_red = 0xFF4500, // rgb(255,69,0) - orchid = 0xDA70D6, // rgb(218,112,214) - pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) - pale_green = 0x98FB98, // rgb(152,251,152) - pale_turquoise = 0xAFEEEE, // rgb(175,238,238) - pale_violet_red = 0xDB7093, // rgb(219,112,147) - papaya_whip = 0xFFEFD5, // rgb(255,239,213) - peach_puff = 0xFFDAB9, // rgb(255,218,185) - peru = 0xCD853F, // rgb(205,133,63) - pink = 0xFFC0CB, // rgb(255,192,203) - plum = 0xDDA0DD, // rgb(221,160,221) - powder_blue = 0xB0E0E6, // rgb(176,224,230) - purple = 0x800080, // rgb(128,0,128) - rebecca_purple = 0x663399, // rgb(102,51,153) - red = 0xFF0000, // rgb(255,0,0) - rosy_brown = 0xBC8F8F, // rgb(188,143,143) - royal_blue = 0x4169E1, // rgb(65,105,225) - saddle_brown = 0x8B4513, // rgb(139,69,19) - salmon = 0xFA8072, // rgb(250,128,114) - sandy_brown = 0xF4A460, // rgb(244,164,96) - sea_green = 0x2E8B57, // rgb(46,139,87) - sea_shell = 0xFFF5EE, // rgb(255,245,238) - sienna = 0xA0522D, // rgb(160,82,45) - silver = 0xC0C0C0, // rgb(192,192,192) - sky_blue = 0x87CEEB, // rgb(135,206,235) - slate_blue = 0x6A5ACD, // rgb(106,90,205) - slate_gray = 0x708090, // rgb(112,128,144) - snow = 0xFFFAFA, // rgb(255,250,250) - spring_green = 0x00FF7F, // rgb(0,255,127) - steel_blue = 0x4682B4, // rgb(70,130,180) - tan = 0xD2B48C, // rgb(210,180,140) - teal = 0x008080, // rgb(0,128,128) - thistle = 0xD8BFD8, // rgb(216,191,216) - tomato = 0xFF6347, // rgb(255,99,71) - turquoise = 0x40E0D0, // rgb(64,224,208) - violet = 0xEE82EE, // rgb(238,130,238) - wheat = 0xF5DEB3, // rgb(245,222,179) - white = 0xFFFFFF, // rgb(255,255,255) - white_smoke = 0xF5F5F5, // rgb(245,245,245) - yellow = 0xFFFF00, // rgb(255,255,0) - yellow_green = 0x9ACD32 // rgb(154,205,50) -}; // enum class color - -enum class terminal_color : uint8_t { - black = 30, - red, - green, - yellow, - blue, - magenta, - cyan, - white, - bright_black = 90, - bright_red, - bright_green, - bright_yellow, - bright_blue, - bright_magenta, - bright_cyan, - bright_white -}; - -enum class emphasis : uint8_t { - bold = 1, - faint = 1 << 1, - italic = 1 << 2, - underline = 1 << 3, - blink = 1 << 4, - reverse = 1 << 5, - conceal = 1 << 6, - strikethrough = 1 << 7, -}; - -// rgb is a struct for red, green and blue colors. -// Using the name "rgb" makes some editors show the color in a tooltip. -struct rgb { - FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} - FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} - FMT_CONSTEXPR rgb(uint32_t hex) - : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} - FMT_CONSTEXPR rgb(color hex) - : r((uint32_t(hex) >> 16) & 0xFF), - g((uint32_t(hex) >> 8) & 0xFF), - b(uint32_t(hex) & 0xFF) {} - uint8_t r; - uint8_t g; - uint8_t b; -}; - -FMT_BEGIN_DETAIL_NAMESPACE - -// color is a struct of either a rgb color or a terminal color. -struct color_type { - FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} - FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { - value.rgb_color = static_cast(rgb_color); - } - FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} { - value.rgb_color = (static_cast(rgb_color.r) << 16) | - (static_cast(rgb_color.g) << 8) | rgb_color.b; - } - FMT_CONSTEXPR color_type(terminal_color term_color) noexcept - : is_rgb(), value{} { - value.term_color = static_cast(term_color); - } - bool is_rgb; - union color_union { - uint8_t term_color; - uint32_t rgb_color; - } value; -}; - -FMT_END_DETAIL_NAMESPACE - -/** A text style consisting of foreground and background colors and emphasis. */ -class text_style { - public: - FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept - : set_foreground_color(), set_background_color(), ems(em) {} - - FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) { - if (!set_foreground_color) { - set_foreground_color = rhs.set_foreground_color; - foreground_color = rhs.foreground_color; - } else if (rhs.set_foreground_color) { - if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) - FMT_THROW(format_error("can't OR a terminal color")); - foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; - } - - if (!set_background_color) { - set_background_color = rhs.set_background_color; - background_color = rhs.background_color; - } else if (rhs.set_background_color) { - if (!background_color.is_rgb || !rhs.background_color.is_rgb) - FMT_THROW(format_error("can't OR a terminal color")); - background_color.value.rgb_color |= rhs.background_color.value.rgb_color; - } - - ems = static_cast(static_cast(ems) | - static_cast(rhs.ems)); - return *this; - } - - friend FMT_CONSTEXPR text_style operator|(text_style lhs, - const text_style& rhs) { - return lhs |= rhs; - } - - FMT_CONSTEXPR bool has_foreground() const noexcept { - return set_foreground_color; - } - FMT_CONSTEXPR bool has_background() const noexcept { - return set_background_color; - } - FMT_CONSTEXPR bool has_emphasis() const noexcept { - return static_cast(ems) != 0; - } - FMT_CONSTEXPR detail::color_type get_foreground() const noexcept { - FMT_ASSERT(has_foreground(), "no foreground specified for this style"); - return foreground_color; - } - FMT_CONSTEXPR detail::color_type get_background() const noexcept { - FMT_ASSERT(has_background(), "no background specified for this style"); - return background_color; - } - FMT_CONSTEXPR emphasis get_emphasis() const noexcept { - FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); - return ems; - } - - private: - FMT_CONSTEXPR text_style(bool is_foreground, - detail::color_type text_color) noexcept - : set_foreground_color(), set_background_color(), ems() { - if (is_foreground) { - foreground_color = text_color; - set_foreground_color = true; - } else { - background_color = text_color; - set_background_color = true; - } - } - - friend FMT_CONSTEXPR text_style fg(detail::color_type foreground) noexcept; - - friend FMT_CONSTEXPR text_style bg(detail::color_type background) noexcept; - - detail::color_type foreground_color; - detail::color_type background_color; - bool set_foreground_color; - bool set_background_color; - emphasis ems; -}; - -/** Creates a text style from the foreground (text) color. */ -FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) noexcept { - return text_style(true, foreground); -} - -/** Creates a text style from the background color. */ -FMT_CONSTEXPR inline text_style bg(detail::color_type background) noexcept { - return text_style(false, background); -} - -FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept { - return text_style(lhs) | rhs; -} - -FMT_BEGIN_DETAIL_NAMESPACE - -template struct ansi_color_escape { - FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, - const char* esc) noexcept { - // If we have a terminal color, we need to output another escape code - // sequence. - if (!text_color.is_rgb) { - bool is_background = esc == string_view("\x1b[48;2;"); - uint32_t value = text_color.value.term_color; - // Background ASCII codes are the same as the foreground ones but with - // 10 more. - if (is_background) value += 10u; - - size_t index = 0; - buffer[index++] = static_cast('\x1b'); - buffer[index++] = static_cast('['); - - if (value >= 100u) { - buffer[index++] = static_cast('1'); - value %= 100u; - } - buffer[index++] = static_cast('0' + value / 10u); - buffer[index++] = static_cast('0' + value % 10u); - - buffer[index++] = static_cast('m'); - buffer[index++] = static_cast('\0'); - return; - } - - for (int i = 0; i < 7; i++) { - buffer[i] = static_cast(esc[i]); - } - rgb color(text_color.value.rgb_color); - to_esc(color.r, buffer + 7, ';'); - to_esc(color.g, buffer + 11, ';'); - to_esc(color.b, buffer + 15, 'm'); - buffer[19] = static_cast(0); - } - FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { - uint8_t em_codes[num_emphases] = {}; - if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1; - if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2; - if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3; - if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4; - if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5; - if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7; - if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; - if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; - - size_t index = 0; - for (size_t i = 0; i < num_emphases; ++i) { - if (!em_codes[i]) continue; - buffer[index++] = static_cast('\x1b'); - buffer[index++] = static_cast('['); - buffer[index++] = static_cast('0' + em_codes[i]); - buffer[index++] = static_cast('m'); - } - buffer[index++] = static_cast(0); - } - FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } - - FMT_CONSTEXPR const Char* begin() const noexcept { return buffer; } - FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const noexcept { - return buffer + std::char_traits::length(buffer); - } - - private: - static constexpr size_t num_emphases = 8; - Char buffer[7u + 3u * num_emphases + 1u]; - - static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, - char delimiter) noexcept { - out[0] = static_cast('0' + c / 100); - out[1] = static_cast('0' + c / 10 % 10); - out[2] = static_cast('0' + c % 10); - out[3] = static_cast(delimiter); - } - static FMT_CONSTEXPR bool has_emphasis(emphasis em, emphasis mask) noexcept { - return static_cast(em) & static_cast(mask); - } -}; - -template -FMT_CONSTEXPR ansi_color_escape make_foreground_color( - detail::color_type foreground) noexcept { - return ansi_color_escape(foreground, "\x1b[38;2;"); -} - -template -FMT_CONSTEXPR ansi_color_escape make_background_color( - detail::color_type background) noexcept { - return ansi_color_escape(background, "\x1b[48;2;"); -} - -template -FMT_CONSTEXPR ansi_color_escape make_emphasis(emphasis em) noexcept { - return ansi_color_escape(em); -} - -template inline void fputs(const Char* chars, FILE* stream) { - int result = std::fputs(chars, stream); - if (result < 0) - FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); -} - -template <> inline void fputs(const wchar_t* chars, FILE* stream) { - int result = std::fputws(chars, stream); - if (result < 0) - FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); -} - -template inline void reset_color(FILE* stream) { - fputs("\x1b[0m", stream); -} - -template <> inline void reset_color(FILE* stream) { - fputs(L"\x1b[0m", stream); -} - -template inline void reset_color(buffer& buffer) { - auto reset_color = string_view("\x1b[0m"); - buffer.append(reset_color.begin(), reset_color.end()); -} - -template struct styled_arg { - const T& value; - text_style style; -}; - -template -void vformat_to(buffer& buf, const text_style& ts, - basic_string_view format_str, - basic_format_args>> args) { - bool has_style = false; - if (ts.has_emphasis()) { - has_style = true; - auto emphasis = detail::make_emphasis(ts.get_emphasis()); - buf.append(emphasis.begin(), emphasis.end()); - } - if (ts.has_foreground()) { - has_style = true; - auto foreground = detail::make_foreground_color(ts.get_foreground()); - buf.append(foreground.begin(), foreground.end()); - } - if (ts.has_background()) { - has_style = true; - auto background = detail::make_background_color(ts.get_background()); - buf.append(background.begin(), background.end()); - } - detail::vformat_to(buf, format_str, args, {}); - if (has_style) detail::reset_color(buf); -} - -FMT_END_DETAIL_NAMESPACE - -template > -void vprint(std::FILE* f, const text_style& ts, const S& format, - basic_format_args>> args) { - basic_memory_buffer buf; - detail::vformat_to(buf, ts, detail::to_string_view(format), args); - if (detail::is_utf8()) { - detail::print(f, basic_string_view(buf.begin(), buf.size())); - } else { - buf.push_back(Char(0)); - detail::fputs(buf.data(), f); - } -} - -/** - \rst - Formats a string and prints it to the specified file stream using ANSI - escape sequences to specify text formatting. - - **Example**:: - - fmt::print(fmt::emphasis::bold | fg(fmt::color::red), - "Elapsed time: {0:.2f} seconds", 1.23); - \endrst - */ -template ::value)> -void print(std::FILE* f, const text_style& ts, const S& format_str, - const Args&... args) { - vprint(f, ts, format_str, - fmt::make_format_args>>(args...)); -} - -/** - \rst - Formats a string and prints it to stdout using ANSI escape sequences to - specify text formatting. - - **Example**:: - - fmt::print(fmt::emphasis::bold | fg(fmt::color::red), - "Elapsed time: {0:.2f} seconds", 1.23); - \endrst - */ -template ::value)> -void print(const text_style& ts, const S& format_str, const Args&... args) { - return print(stdout, ts, format_str, args...); -} - -template > -inline std::basic_string vformat( - const text_style& ts, const S& format_str, - basic_format_args>> args) { - basic_memory_buffer buf; - detail::vformat_to(buf, ts, detail::to_string_view(format_str), args); - return fmt::to_string(buf); -} - -/** - \rst - Formats arguments and returns the result as a string using ANSI - escape sequences to specify text formatting. - - **Example**:: - - #include - std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), - "The answer is {}", 42); - \endrst -*/ -template > -inline std::basic_string format(const text_style& ts, const S& format_str, - const Args&... args) { - return fmt::vformat(ts, detail::to_string_view(format_str), - fmt::make_format_args>(args...)); -} - -/** - Formats a string with the given text_style and writes the output to ``out``. - */ -template ::value)> -OutputIt vformat_to( - OutputIt out, const text_style& ts, basic_string_view format_str, - basic_format_args>> args) { - auto&& buf = detail::get_buffer(out); - detail::vformat_to(buf, ts, format_str, args); - return detail::get_iterator(buf); -} - -/** - \rst - Formats arguments with the given text_style, writes the result to the output - iterator ``out`` and returns the iterator past the end of the output range. - - **Example**:: - - std::vector out; - fmt::format_to(std::back_inserter(out), - fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); - \endrst -*/ -template >::value&& - detail::is_string::value> -inline auto format_to(OutputIt out, const text_style& ts, const S& format_str, - Args&&... args) -> - typename std::enable_if::type { - return vformat_to(out, ts, detail::to_string_view(format_str), - fmt::make_format_args>>(args...)); -} - -template -struct formatter, Char> : formatter { - template - auto format(const detail::styled_arg& arg, FormatContext& ctx) const - -> decltype(ctx.out()) { - const auto& ts = arg.style; - const auto& value = arg.value; - auto out = ctx.out(); - - bool has_style = false; - if (ts.has_emphasis()) { - has_style = true; - auto emphasis = detail::make_emphasis(ts.get_emphasis()); - out = std::copy(emphasis.begin(), emphasis.end(), out); - } - if (ts.has_foreground()) { - has_style = true; - auto foreground = - detail::make_foreground_color(ts.get_foreground()); - out = std::copy(foreground.begin(), foreground.end(), out); - } - if (ts.has_background()) { - has_style = true; - auto background = - detail::make_background_color(ts.get_background()); - out = std::copy(background.begin(), background.end(), out); - } - out = formatter::format(value, ctx); - if (has_style) { - auto reset_color = string_view("\x1b[0m"); - out = std::copy(reset_color.begin(), reset_color.end(), out); - } - return out; - } -}; - -/** - \rst - Returns an argument that will be formatted using ANSI escape sequences, - to be used in a formatting function. - - **Example**:: - - fmt::print("Elapsed time: {0:.2f} seconds", - fmt::styled(1.23, fmt::fg(fmt::color::green) | - fmt::bg(fmt::color::blue))); - \endrst - */ -template -FMT_CONSTEXPR auto styled(const T& value, text_style ts) - -> detail::styled_arg> { - return detail::styled_arg>{value, ts}; -} - -FMT_MODULE_EXPORT_END -FMT_END_NAMESPACE - -#endif // FMT_COLOR_H_ diff --git a/Externals/fmt/include/fmt/compile.h b/Externals/fmt/include/fmt/compile.h deleted file mode 100644 index 933668c41c..0000000000 --- a/Externals/fmt/include/fmt/compile.h +++ /dev/null @@ -1,611 +0,0 @@ -// Formatting library for C++ - experimental format string compilation -// -// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_COMPILE_H_ -#define FMT_COMPILE_H_ - -#include "format.h" - -FMT_BEGIN_NAMESPACE -namespace detail { - -template -FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end, - counting_iterator it) { - return it + (end - begin); -} - -template class truncating_iterator_base { - protected: - OutputIt out_; - size_t limit_; - size_t count_ = 0; - - truncating_iterator_base() : out_(), limit_(0) {} - - truncating_iterator_base(OutputIt out, size_t limit) - : out_(out), limit_(limit) {} - - public: - using iterator_category = std::output_iterator_tag; - using value_type = typename std::iterator_traits::value_type; - using difference_type = std::ptrdiff_t; - using pointer = void; - using reference = void; - FMT_UNCHECKED_ITERATOR(truncating_iterator_base); - - OutputIt base() const { return out_; } - size_t count() const { return count_; } -}; - -// An output iterator that truncates the output and counts the number of objects -// written to it. -template ::value_type>::type> -class truncating_iterator; - -template -class truncating_iterator - : public truncating_iterator_base { - mutable typename truncating_iterator_base::value_type blackhole_; - - public: - using value_type = typename truncating_iterator_base::value_type; - - truncating_iterator() = default; - - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} - - truncating_iterator& operator++() { - if (this->count_++ < this->limit_) ++this->out_; - return *this; - } - - truncating_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - value_type& operator*() const { - return this->count_ < this->limit_ ? *this->out_ : blackhole_; - } -}; - -template -class truncating_iterator - : public truncating_iterator_base { - public: - truncating_iterator() = default; - - truncating_iterator(OutputIt out, size_t limit) - : truncating_iterator_base(out, limit) {} - - template truncating_iterator& operator=(T val) { - if (this->count_++ < this->limit_) *this->out_++ = val; - return *this; - } - - truncating_iterator& operator++() { return *this; } - truncating_iterator& operator++(int) { return *this; } - truncating_iterator& operator*() { return *this; } -}; - -// A compile-time string which is compiled into fast formatting code. -class compiled_string {}; - -template -struct is_compiled_string : std::is_base_of {}; - -/** - \rst - Converts a string literal *s* into a format string that will be parsed at - compile time and converted into efficient formatting code. Requires C++17 - ``constexpr if`` compiler support. - - **Example**:: - - // Converts 42 into std::string using the most efficient method and no - // runtime format string processing. - std::string s = fmt::format(FMT_COMPILE("{}"), 42); - \endrst - */ -#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) -# define FMT_COMPILE(s) \ - FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit) -#else -# define FMT_COMPILE(s) FMT_STRING(s) -#endif - -#if FMT_USE_NONTYPE_TEMPLATE_ARGS -template Str> -struct udl_compiled_string : compiled_string { - using char_type = Char; - explicit constexpr operator basic_string_view() const { - return {Str.data, N - 1}; - } -}; -#endif - -template -const T& first(const T& value, const Tail&...) { - return value; -} - -#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) -template struct type_list {}; - -// Returns a reference to the argument at index N from [first, rest...]. -template -constexpr const auto& get([[maybe_unused]] const T& first, - [[maybe_unused]] const Args&... rest) { - static_assert(N < 1 + sizeof...(Args), "index is out of bounds"); - if constexpr (N == 0) - return first; - else - return detail::get(rest...); -} - -template -constexpr int get_arg_index_by_name(basic_string_view name, - type_list) { - return get_arg_index_by_name(name); -} - -template struct get_type_impl; - -template struct get_type_impl> { - using type = - remove_cvref_t(std::declval()...))>; -}; - -template -using get_type = typename get_type_impl::type; - -template struct is_compiled_format : std::false_type {}; - -template struct text { - basic_string_view data; - using char_type = Char; - - template - constexpr OutputIt format(OutputIt out, const Args&...) const { - return write(out, data); - } -}; - -template -struct is_compiled_format> : std::true_type {}; - -template -constexpr text make_text(basic_string_view s, size_t pos, - size_t size) { - return {{&s[pos], size}}; -} - -template struct code_unit { - Char value; - using char_type = Char; - - template - constexpr OutputIt format(OutputIt out, const Args&...) const { - return write(out, value); - } -}; - -// This ensures that the argument type is convertible to `const T&`. -template -constexpr const T& get_arg_checked(const Args&... args) { - const auto& arg = detail::get(args...); - if constexpr (detail::is_named_arg>()) { - return arg.value; - } else { - return arg; - } -} - -template -struct is_compiled_format> : std::true_type {}; - -// A replacement field that refers to argument N. -template struct field { - using char_type = Char; - - template - constexpr OutputIt format(OutputIt out, const Args&... args) const { - return write(out, get_arg_checked(args...)); - } -}; - -template -struct is_compiled_format> : std::true_type {}; - -// A replacement field that refers to argument with name. -template struct runtime_named_field { - using char_type = Char; - basic_string_view name; - - template - constexpr static bool try_format_argument( - OutputIt& out, - // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9 - [[maybe_unused]] basic_string_view arg_name, const T& arg) { - if constexpr (is_named_arg::type>::value) { - if (arg_name == arg.name) { - out = write(out, arg.value); - return true; - } - } - return false; - } - - template - constexpr OutputIt format(OutputIt out, const Args&... args) const { - bool found = (try_format_argument(out, name, args) || ...); - if (!found) { - FMT_THROW(format_error("argument with specified name is not found")); - } - return out; - } -}; - -template -struct is_compiled_format> : std::true_type {}; - -// A replacement field that refers to argument N and has format specifiers. -template struct spec_field { - using char_type = Char; - formatter fmt; - - template - constexpr FMT_INLINE OutputIt format(OutputIt out, - const Args&... args) const { - const auto& vargs = - fmt::make_format_args>(args...); - basic_format_context ctx(out, vargs); - return fmt.format(get_arg_checked(args...), ctx); - } -}; - -template -struct is_compiled_format> : std::true_type {}; - -template struct concat { - L lhs; - R rhs; - using char_type = typename L::char_type; - - template - constexpr OutputIt format(OutputIt out, const Args&... args) const { - out = lhs.format(out, args...); - return rhs.format(out, args...); - } -}; - -template -struct is_compiled_format> : std::true_type {}; - -template -constexpr concat make_concat(L lhs, R rhs) { - return {lhs, rhs}; -} - -struct unknown_format {}; - -template -constexpr size_t parse_text(basic_string_view str, size_t pos) { - for (size_t size = str.size(); pos != size; ++pos) { - if (str[pos] == '{' || str[pos] == '}') break; - } - return pos; -} - -template -constexpr auto compile_format_string(S format_str); - -template -constexpr auto parse_tail(T head, S format_str) { - if constexpr (POS != - basic_string_view(format_str).size()) { - constexpr auto tail = compile_format_string(format_str); - if constexpr (std::is_same, - unknown_format>()) - return tail; - else - return make_concat(head, tail); - } else { - return head; - } -} - -template struct parse_specs_result { - formatter fmt; - size_t end; - int next_arg_id; -}; - -constexpr int manual_indexing_id = -1; - -template -constexpr parse_specs_result parse_specs(basic_string_view str, - size_t pos, int next_arg_id) { - str.remove_prefix(pos); - auto ctx = compile_parse_context(str, max_value(), nullptr, {}, - next_arg_id); - auto f = formatter(); - auto end = f.parse(ctx); - return {f, pos + fmt::detail::to_unsigned(end - str.data()), - next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()}; -} - -template struct arg_id_handler { - arg_ref arg_id; - - constexpr int operator()() { - FMT_ASSERT(false, "handler cannot be used with automatic indexing"); - return 0; - } - constexpr int operator()(int id) { - arg_id = arg_ref(id); - return 0; - } - constexpr int operator()(basic_string_view id) { - arg_id = arg_ref(id); - return 0; - } - - constexpr void on_error(const char* message) { - FMT_THROW(format_error(message)); - } -}; - -template struct parse_arg_id_result { - arg_ref arg_id; - const Char* arg_id_end; -}; - -template -constexpr auto parse_arg_id(const Char* begin, const Char* end) { - auto handler = arg_id_handler{arg_ref{}}; - auto arg_id_end = parse_arg_id(begin, end, handler); - return parse_arg_id_result{handler.arg_id, arg_id_end}; -} - -template struct field_type { - using type = remove_cvref_t; -}; - -template -struct field_type::value>> { - using type = remove_cvref_t; -}; - -template -constexpr auto parse_replacement_field_then_tail(S format_str) { - using char_type = typename S::char_type; - constexpr auto str = basic_string_view(format_str); - constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); - if constexpr (c == '}') { - return parse_tail( - field::type, ARG_INDEX>(), - format_str); - } else if constexpr (c != ':') { - FMT_THROW(format_error("expected ':'")); - } else { - constexpr auto result = parse_specs::type>( - str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID); - if constexpr (result.end >= str.size() || str[result.end] != '}') { - FMT_THROW(format_error("expected '}'")); - return 0; - } else { - return parse_tail( - spec_field::type, ARG_INDEX>{ - result.fmt}, - format_str); - } - } -} - -// Compiles a non-empty format string and returns the compiled representation -// or unknown_format() on unrecognized input. -template -constexpr auto compile_format_string(S format_str) { - using char_type = typename S::char_type; - constexpr auto str = basic_string_view(format_str); - if constexpr (str[POS] == '{') { - if constexpr (POS + 1 == str.size()) - FMT_THROW(format_error("unmatched '{' in format string")); - if constexpr (str[POS + 1] == '{') { - return parse_tail(make_text(str, POS, 1), format_str); - } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { - static_assert(ID != manual_indexing_id, - "cannot switch from manual to automatic argument indexing"); - constexpr auto next_id = - ID != manual_indexing_id ? ID + 1 : manual_indexing_id; - return parse_replacement_field_then_tail, Args, - POS + 1, ID, next_id>( - format_str); - } else { - constexpr auto arg_id_result = - parse_arg_id(str.data() + POS + 1, str.data() + str.size()); - constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data(); - constexpr char_type c = - arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); - static_assert(c == '}' || c == ':', "missing '}' in format string"); - if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) { - static_assert( - ID == manual_indexing_id || ID == 0, - "cannot switch from automatic to manual argument indexing"); - constexpr auto arg_index = arg_id_result.arg_id.val.index; - return parse_replacement_field_then_tail, - Args, arg_id_end_pos, - arg_index, manual_indexing_id>( - format_str); - } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { - constexpr auto arg_index = - get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); - if constexpr (arg_index != invalid_arg_index) { - constexpr auto next_id = - ID != manual_indexing_id ? ID + 1 : manual_indexing_id; - return parse_replacement_field_then_tail< - decltype(get_type::value), Args, arg_id_end_pos, - arg_index, next_id>(format_str); - } else { - if constexpr (c == '}') { - return parse_tail( - runtime_named_field{arg_id_result.arg_id.val.name}, - format_str); - } else if constexpr (c == ':') { - return unknown_format(); // no type info for specs parsing - } - } - } - } - } else if constexpr (str[POS] == '}') { - if constexpr (POS + 1 == str.size()) - FMT_THROW(format_error("unmatched '}' in format string")); - return parse_tail(make_text(str, POS, 1), format_str); - } else { - constexpr auto end = parse_text(str, POS + 1); - if constexpr (end - POS > 1) { - return parse_tail(make_text(str, POS, end - POS), - format_str); - } else { - return parse_tail(code_unit{str[POS]}, - format_str); - } - } -} - -template ::value)> -constexpr auto compile(S format_str) { - constexpr auto str = basic_string_view(format_str); - if constexpr (str.size() == 0) { - return detail::make_text(str, 0, 0); - } else { - constexpr auto result = - detail::compile_format_string, 0, 0>( - format_str); - return result; - } -} -#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) -} // namespace detail - -FMT_MODULE_EXPORT_BEGIN - -#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) - -template ::value)> -FMT_INLINE std::basic_string format(const CompiledFormat& cf, - const Args&... args) { - auto s = std::basic_string(); - cf.format(std::back_inserter(s), args...); - return s; -} - -template ::value)> -constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, - const Args&... args) { - return cf.format(out, args...); -} - -template ::value)> -FMT_INLINE std::basic_string format(const S&, - Args&&... args) { - if constexpr (std::is_same::value) { - constexpr auto str = basic_string_view(S()); - if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { - const auto& first = detail::first(args...); - if constexpr (detail::is_named_arg< - remove_cvref_t>::value) { - return fmt::to_string(first.value); - } else { - return fmt::to_string(first); - } - } - } - constexpr auto compiled = detail::compile(S()); - if constexpr (std::is_same, - detail::unknown_format>()) { - return fmt::format( - static_cast>(S()), - std::forward(args)...); - } else { - return fmt::format(compiled, std::forward(args)...); - } -} - -template ::value)> -FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { - constexpr auto compiled = detail::compile(S()); - if constexpr (std::is_same, - detail::unknown_format>()) { - return fmt::format_to( - out, static_cast>(S()), - std::forward(args)...); - } else { - return fmt::format_to(out, compiled, std::forward(args)...); - } -} -#endif - -template ::value)> -format_to_n_result format_to_n(OutputIt out, size_t n, - const S& format_str, Args&&... args) { - auto it = fmt::format_to(detail::truncating_iterator(out, n), - format_str, std::forward(args)...); - return {it.base(), it.count()}; -} - -template ::value)> -FMT_CONSTEXPR20 size_t formatted_size(const S& format_str, - const Args&... args) { - return fmt::format_to(detail::counting_iterator(), format_str, args...) - .count(); -} - -template ::value)> -void print(std::FILE* f, const S& format_str, const Args&... args) { - memory_buffer buffer; - fmt::format_to(std::back_inserter(buffer), format_str, args...); - detail::print(f, {buffer.data(), buffer.size()}); -} - -template ::value)> -void print(const S& format_str, const Args&... args) { - print(stdout, format_str, args...); -} - -#if FMT_USE_NONTYPE_TEMPLATE_ARGS -inline namespace literals { -template constexpr auto operator""_cf() { - using char_t = remove_cvref_t; - return detail::udl_compiled_string(); -} -} // namespace literals -#endif - -FMT_MODULE_EXPORT_END -FMT_END_NAMESPACE - -#endif // FMT_COMPILE_H_ diff --git a/Externals/fmt/include/fmt/core.h b/Externals/fmt/include/fmt/core.h deleted file mode 100755 index f6a37af9e3..0000000000 --- a/Externals/fmt/include/fmt/core.h +++ /dev/null @@ -1,3323 +0,0 @@ -// Formatting library for C++ - the core API for char/UTF-8 -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_CORE_H_ -#define FMT_CORE_H_ - -#include // std::byte -#include // std::FILE -#include // std::strlen -#include -#include -#include -#include - -// The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 90100 - -#if defined(__clang__) && !defined(__ibmxl__) -# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) -#else -# define FMT_CLANG_VERSION 0 -#endif - -#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && \ - !defined(__NVCOMPILER) -# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -#else -# define FMT_GCC_VERSION 0 -#endif - -#ifndef FMT_GCC_PRAGMA -// Workaround _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884. -# if FMT_GCC_VERSION >= 504 -# define FMT_GCC_PRAGMA(arg) _Pragma(arg) -# else -# define FMT_GCC_PRAGMA(arg) -# endif -#endif - -#ifdef __ICL -# define FMT_ICC_VERSION __ICL -#elif defined(__INTEL_COMPILER) -# define FMT_ICC_VERSION __INTEL_COMPILER -#else -# define FMT_ICC_VERSION 0 -#endif - -#ifdef _MSC_VER -# define FMT_MSC_VERSION _MSC_VER -# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) -#else -# define FMT_MSC_VERSION 0 -# define FMT_MSC_WARNING(...) -#endif - -#ifdef _MSVC_LANG -# define FMT_CPLUSPLUS _MSVC_LANG -#else -# define FMT_CPLUSPLUS __cplusplus -#endif - -#ifdef __has_feature -# define FMT_HAS_FEATURE(x) __has_feature(x) -#else -# define FMT_HAS_FEATURE(x) 0 -#endif - -#if (defined(__has_include) || FMT_ICC_VERSION >= 1600 || \ - FMT_MSC_VERSION > 1900) && \ - !defined(__INTELLISENSE__) -# define FMT_HAS_INCLUDE(x) __has_include(x) -#else -# define FMT_HAS_INCLUDE(x) 0 -#endif - -#ifdef __has_cpp_attribute -# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) -#else -# define FMT_HAS_CPP_ATTRIBUTE(x) 0 -#endif - -#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ - (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) - -#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ - (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) - -// Check if relaxed C++14 constexpr is supported. -// GCC doesn't allow throw in constexpr until version 6 (bug 67371). -#ifndef FMT_USE_CONSTEXPR -# if (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 || \ - (FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L)) && \ - !FMT_ICC_VERSION && !defined(__NVCC__) -# define FMT_USE_CONSTEXPR 1 -# else -# define FMT_USE_CONSTEXPR 0 -# endif -#endif -#if FMT_USE_CONSTEXPR -# define FMT_CONSTEXPR constexpr -#else -# define FMT_CONSTEXPR -#endif - -#if ((FMT_CPLUSPLUS >= 202002L) && \ - (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9)) || \ - (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002) -# define FMT_CONSTEXPR20 constexpr -#else -# define FMT_CONSTEXPR20 -#endif - -// Check if constexpr std::char_traits<>::{compare,length} are supported. -#if defined(__GLIBCXX__) -# if FMT_CPLUSPLUS >= 201703L && defined(_GLIBCXX_RELEASE) && \ - _GLIBCXX_RELEASE >= 7 // GCC 7+ libstdc++ has _GLIBCXX_RELEASE. -# define FMT_CONSTEXPR_CHAR_TRAITS constexpr -# endif -#elif defined(_LIBCPP_VERSION) && FMT_CPLUSPLUS >= 201703L && \ - _LIBCPP_VERSION >= 4000 -# define FMT_CONSTEXPR_CHAR_TRAITS constexpr -#elif FMT_MSC_VERSION >= 1914 && FMT_CPLUSPLUS >= 201703L -# define FMT_CONSTEXPR_CHAR_TRAITS constexpr -#endif -#ifndef FMT_CONSTEXPR_CHAR_TRAITS -# define FMT_CONSTEXPR_CHAR_TRAITS -#endif - -// Check if exceptions are disabled. -#ifndef FMT_EXCEPTIONS -# if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \ - (FMT_MSC_VERSION && !_HAS_EXCEPTIONS) -# define FMT_EXCEPTIONS 0 -# else -# define FMT_EXCEPTIONS 1 -# endif -#endif - -#ifndef FMT_DEPRECATED -# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VERSION >= 1900 -# define FMT_DEPRECATED [[deprecated]] -# else -# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) -# define FMT_DEPRECATED __attribute__((deprecated)) -# elif FMT_MSC_VERSION -# define FMT_DEPRECATED __declspec(deprecated) -# else -# define FMT_DEPRECATED /* deprecated */ -# endif -# endif -#endif - -// [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code -// warnings. -#if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && \ - !defined(__NVCC__) -# define FMT_NORETURN [[noreturn]] -#else -# define FMT_NORETURN -#endif - -#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) -# define FMT_FALLTHROUGH [[fallthrough]] -#elif defined(__clang__) -# define FMT_FALLTHROUGH [[clang::fallthrough]] -#elif FMT_GCC_VERSION >= 700 && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) -# define FMT_FALLTHROUGH [[gnu::fallthrough]] -#else -# define FMT_FALLTHROUGH -#endif - -#ifndef FMT_NODISCARD -# if FMT_HAS_CPP17_ATTRIBUTE(nodiscard) -# define FMT_NODISCARD [[nodiscard]] -# else -# define FMT_NODISCARD -# endif -#endif - -#ifndef FMT_USE_FLOAT -# define FMT_USE_FLOAT 1 -#endif -#ifndef FMT_USE_DOUBLE -# define FMT_USE_DOUBLE 1 -#endif -#ifndef FMT_USE_LONG_DOUBLE -# define FMT_USE_LONG_DOUBLE 1 -#endif - -#ifndef FMT_INLINE -# if FMT_GCC_VERSION || FMT_CLANG_VERSION -# define FMT_INLINE inline __attribute__((always_inline)) -# else -# define FMT_INLINE inline -# endif -#endif - -// An inline std::forward replacement. -#define FMT_FORWARD(...) static_cast(__VA_ARGS__) - -#ifdef _MSC_VER -# define FMT_UNCHECKED_ITERATOR(It) \ - using _Unchecked_type = It // Mark iterator as checked. -#else -# define FMT_UNCHECKED_ITERATOR(It) using unchecked_type = It -#endif - -#ifndef FMT_BEGIN_NAMESPACE -# define FMT_BEGIN_NAMESPACE \ - namespace fmt { \ - inline namespace v9 { -# define FMT_END_NAMESPACE \ - } \ - } -#endif - -#ifndef FMT_MODULE_EXPORT -# define FMT_MODULE_EXPORT -# define FMT_MODULE_EXPORT_BEGIN -# define FMT_MODULE_EXPORT_END -# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { -# define FMT_END_DETAIL_NAMESPACE } -#endif - -#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275) -# ifdef FMT_EXPORT -# define FMT_API __declspec(dllexport) -# elif defined(FMT_SHARED) -# define FMT_API __declspec(dllimport) -# endif -#else -# define FMT_CLASS_API -# if defined(FMT_EXPORT) || defined(FMT_SHARED) -# if defined(__GNUC__) || defined(__clang__) -# define FMT_API __attribute__((visibility("default"))) -# endif -# endif -#endif -#ifndef FMT_API -# define FMT_API -#endif - -// libc++ supports string_view in pre-c++17. -#if FMT_HAS_INCLUDE() && \ - (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) -# include -# define FMT_USE_STRING_VIEW -#elif FMT_HAS_INCLUDE("experimental/string_view") && FMT_CPLUSPLUS >= 201402L -# include -# define FMT_USE_EXPERIMENTAL_STRING_VIEW -#endif - -#ifndef FMT_UNICODE -# define FMT_UNICODE !FMT_MSC_VERSION -#endif - -#ifndef FMT_CONSTEVAL -# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ - FMT_CPLUSPLUS >= 202002L && !defined(__apple_build_version__)) || \ - (defined(__cpp_consteval) && \ - (!FMT_MSC_VERSION || _MSC_FULL_VER >= 193030704)) -// consteval is broken in MSVC before VS2022 and Apple clang 13. -# define FMT_CONSTEVAL consteval -# define FMT_HAS_CONSTEVAL -# else -# define FMT_CONSTEVAL -# endif -#endif - -#ifndef FMT_USE_NONTYPE_TEMPLATE_ARGS -# if defined(__cpp_nontype_template_args) && \ - ((FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L) || \ - __cpp_nontype_template_args >= 201911L) && \ - !defined(__NVCOMPILER) -# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 -# else -# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 -# endif -#endif - -// Enable minimal optimizations for more compact code in debug mode. -FMT_GCC_PRAGMA("GCC push_options") -#if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) -FMT_GCC_PRAGMA("GCC optimize(\"Og\")") -#endif - -FMT_BEGIN_NAMESPACE -FMT_MODULE_EXPORT_BEGIN - -// Implementations of enable_if_t and other metafunctions for older systems. -template -using enable_if_t = typename std::enable_if::type; -template -using conditional_t = typename std::conditional::type; -template using bool_constant = std::integral_constant; -template -using remove_reference_t = typename std::remove_reference::type; -template -using remove_const_t = typename std::remove_const::type; -template -using remove_cvref_t = typename std::remove_cv>::type; -template struct type_identity { using type = T; }; -template using type_identity_t = typename type_identity::type; -template -using underlying_t = typename std::underlying_type::type; - -template struct disjunction : std::false_type {}; -template struct disjunction

: P {}; -template -struct disjunction - : conditional_t> {}; - -template struct conjunction : std::true_type {}; -template struct conjunction

: P {}; -template -struct conjunction - : conditional_t, P1> {}; - -struct monostate { - constexpr monostate() {} -}; - -// An enable_if helper to be used in template parameters which results in much -// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed -// to workaround a bug in MSVC 2019 (see #1140 and #1186). -#ifdef FMT_DOC -# define FMT_ENABLE_IF(...) -#else -# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 -#endif - -FMT_BEGIN_DETAIL_NAMESPACE - -// Suppresses "unused variable" warnings with the method described in -// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. -// (void)var does not work on many Intel compilers. -template FMT_CONSTEXPR void ignore_unused(const T&...) {} - -constexpr FMT_INLINE auto is_constant_evaluated( - bool default_value = false) noexcept -> bool { -#ifdef __cpp_lib_is_constant_evaluated - ignore_unused(default_value); - return std::is_constant_evaluated(); -#else - return default_value; -#endif -} - -// Suppresses "conditional expression is constant" warnings. -template constexpr FMT_INLINE auto const_check(T value) -> T { - return value; -} - -FMT_NORETURN FMT_API void assert_fail(const char* file, int line, - const char* message); - -#ifndef FMT_ASSERT -# ifdef NDEBUG -// FMT_ASSERT is not empty to avoid -Wempty-body. -# define FMT_ASSERT(condition, message) \ - ::fmt::detail::ignore_unused((condition), (message)) -# else -# define FMT_ASSERT(condition, message) \ - ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ - ? (void)0 \ - : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message))) -# endif -#endif - -#if defined(FMT_USE_STRING_VIEW) -template using std_string_view = std::basic_string_view; -#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) -template -using std_string_view = std::experimental::basic_string_view; -#else -template struct std_string_view {}; -#endif - -#ifdef FMT_USE_INT128 -// Do nothing. -#elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ - !(FMT_CLANG_VERSION && FMT_MSC_VERSION) -# define FMT_USE_INT128 1 -using int128_opt = __int128_t; // An optional native 128-bit integer. -using uint128_opt = __uint128_t; -template inline auto convert_for_visit(T value) -> T { - return value; -} -#else -# define FMT_USE_INT128 0 -#endif -#if !FMT_USE_INT128 -enum class int128_opt {}; -enum class uint128_opt {}; -// Reduce template instantiations. -template auto convert_for_visit(T) -> monostate { return {}; } -#endif - -// Casts a nonnegative integer to unsigned. -template -FMT_CONSTEXPR auto to_unsigned(Int value) -> - typename std::make_unsigned::type { - FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); - return static_cast::type>(value); -} - -FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5"; - -constexpr auto is_utf8() -> bool { - // Avoid buggy sign extensions in MSVC's constant evaluation mode (#2297). - using uchar = unsigned char; - return FMT_UNICODE || (sizeof(micro) == 3 && uchar(micro[0]) == 0xC2 && - uchar(micro[1]) == 0xB5); -} -FMT_END_DETAIL_NAMESPACE - -/** - An implementation of ``std::basic_string_view`` for pre-C++17. It provides a - subset of the API. ``fmt::basic_string_view`` is used for format strings even - if ``std::string_view`` is available to prevent issues when a library is - compiled with a different ``-std`` option than the client code (which is not - recommended). - */ -template class basic_string_view { - private: - const Char* data_; - size_t size_; - - public: - using value_type = Char; - using iterator = const Char*; - - constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} - - /** Constructs a string reference object from a C string and a size. */ - constexpr basic_string_view(const Char* s, size_t count) noexcept - : data_(s), size_(count) {} - - /** - \rst - Constructs a string reference object from a C string computing - the size with ``std::char_traits::length``. - \endrst - */ - FMT_CONSTEXPR_CHAR_TRAITS - FMT_INLINE - basic_string_view(const Char* s) - : data_(s), - size_(detail::const_check(std::is_same::value && - !detail::is_constant_evaluated(true)) - ? std::strlen(reinterpret_cast(s)) - : std::char_traits::length(s)) {} - - /** Constructs a string reference from a ``std::basic_string`` object. */ - template - FMT_CONSTEXPR basic_string_view( - const std::basic_string& s) noexcept - : data_(s.data()), size_(s.size()) {} - - template >::value)> - FMT_CONSTEXPR basic_string_view(S s) noexcept - : data_(s.data()), size_(s.size()) {} - - /** Returns a pointer to the string data. */ - constexpr auto data() const noexcept -> const Char* { return data_; } - - /** Returns the string size. */ - constexpr auto size() const noexcept -> size_t { return size_; } - - constexpr auto begin() const noexcept -> iterator { return data_; } - constexpr auto end() const noexcept -> iterator { return data_ + size_; } - - constexpr auto operator[](size_t pos) const noexcept -> const Char& { - return data_[pos]; - } - - FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { - data_ += n; - size_ -= n; - } - - // Lexicographically compare this string reference to other. - FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { - size_t str_size = size_ < other.size_ ? size_ : other.size_; - int result = std::char_traits::compare(data_, other.data_, str_size); - if (result == 0) - result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); - return result; - } - - FMT_CONSTEXPR_CHAR_TRAITS friend auto operator==(basic_string_view lhs, - basic_string_view rhs) - -> bool { - return lhs.compare(rhs) == 0; - } - friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) != 0; - } - friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) < 0; - } - friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) <= 0; - } - friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) > 0; - } - friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) >= 0; - } -}; - -using string_view = basic_string_view; - -/** Specifies if ``T`` is a character type. Can be specialized by users. */ -template struct is_char : std::false_type {}; -template <> struct is_char : std::true_type {}; - -FMT_BEGIN_DETAIL_NAMESPACE - -// A base class for compile-time strings. -struct compile_string {}; - -template -struct is_compile_string : std::is_base_of {}; - -// Returns a string view of `s`. -template ::value)> -FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view { - return s; -} -template -inline auto to_string_view(const std::basic_string& s) - -> basic_string_view { - return s; -} -template -constexpr auto to_string_view(basic_string_view s) - -> basic_string_view { - return s; -} -template >::value)> -inline auto to_string_view(std_string_view s) -> basic_string_view { - return s; -} -template ::value)> -constexpr auto to_string_view(const S& s) - -> basic_string_view { - return basic_string_view(s); -} -void to_string_view(...); - -// Specifies whether S is a string type convertible to fmt::basic_string_view. -// It should be a constexpr function but MSVC 2017 fails to compile it in -// enable_if and MSVC 2015 fails to compile it as an alias template. -// ADL invocation of to_string_view is DEPRECATED! -template -struct is_string : std::is_class()))> { -}; - -template struct char_t_impl {}; -template struct char_t_impl::value>> { - using result = decltype(to_string_view(std::declval())); - using type = typename result::value_type; -}; - -enum class type { - none_type, - // Integer types should go first, - int_type, - uint_type, - long_long_type, - ulong_long_type, - int128_type, - uint128_type, - bool_type, - char_type, - last_integer_type = char_type, - // followed by floating-point types. - float_type, - double_type, - long_double_type, - last_numeric_type = long_double_type, - cstring_type, - string_type, - pointer_type, - custom_type -}; - -// Maps core type T to the corresponding type enum constant. -template -struct type_constant : std::integral_constant {}; - -#define FMT_TYPE_CONSTANT(Type, constant) \ - template \ - struct type_constant \ - : std::integral_constant {} - -FMT_TYPE_CONSTANT(int, int_type); -FMT_TYPE_CONSTANT(unsigned, uint_type); -FMT_TYPE_CONSTANT(long long, long_long_type); -FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); -FMT_TYPE_CONSTANT(int128_opt, int128_type); -FMT_TYPE_CONSTANT(uint128_opt, uint128_type); -FMT_TYPE_CONSTANT(bool, bool_type); -FMT_TYPE_CONSTANT(Char, char_type); -FMT_TYPE_CONSTANT(float, float_type); -FMT_TYPE_CONSTANT(double, double_type); -FMT_TYPE_CONSTANT(long double, long_double_type); -FMT_TYPE_CONSTANT(const Char*, cstring_type); -FMT_TYPE_CONSTANT(basic_string_view, string_type); -FMT_TYPE_CONSTANT(const void*, pointer_type); - -constexpr bool is_integral_type(type t) { - return t > type::none_type && t <= type::last_integer_type; -} - -constexpr bool is_arithmetic_type(type t) { - return t > type::none_type && t <= type::last_numeric_type; -} - -FMT_NORETURN FMT_API void throw_format_error(const char* message); - -struct error_handler { - constexpr error_handler() = default; - constexpr error_handler(const error_handler&) = default; - - // This function is intentionally not constexpr to give a compile-time error. - FMT_NORETURN void on_error(const char* message) { - throw_format_error(message); - } -}; -FMT_END_DETAIL_NAMESPACE - -/** String's character type. */ -template using char_t = typename detail::char_t_impl::type; - -/** - \rst - Parsing context consisting of a format string range being parsed and an - argument counter for automatic indexing. - You can use the ``format_parse_context`` type alias for ``char`` instead. - \endrst - */ -template -class basic_format_parse_context : private ErrorHandler { - private: - basic_string_view format_str_; - int next_arg_id_; - - FMT_CONSTEXPR void do_check_arg_id(int id); - - public: - using char_type = Char; - using iterator = typename basic_string_view::iterator; - - explicit constexpr basic_format_parse_context( - basic_string_view format_str, ErrorHandler eh = {}, - int next_arg_id = 0) - : ErrorHandler(eh), format_str_(format_str), next_arg_id_(next_arg_id) {} - - /** - Returns an iterator to the beginning of the format string range being - parsed. - */ - constexpr auto begin() const noexcept -> iterator { - return format_str_.begin(); - } - - /** - Returns an iterator past the end of the format string range being parsed. - */ - constexpr auto end() const noexcept -> iterator { return format_str_.end(); } - - /** Advances the begin iterator to ``it``. */ - FMT_CONSTEXPR void advance_to(iterator it) { - format_str_.remove_prefix(detail::to_unsigned(it - begin())); - } - - /** - Reports an error if using the manual argument indexing; otherwise returns - the next argument index and switches to the automatic indexing. - */ - FMT_CONSTEXPR auto next_arg_id() -> int { - if (next_arg_id_ < 0) { - on_error("cannot switch from manual to automatic argument indexing"); - return 0; - } - int id = next_arg_id_++; - do_check_arg_id(id); - return id; - } - - /** - Reports an error if using the automatic argument indexing; otherwise - switches to the manual indexing. - */ - FMT_CONSTEXPR void check_arg_id(int id) { - if (next_arg_id_ > 0) { - on_error("cannot switch from automatic to manual argument indexing"); - return; - } - next_arg_id_ = -1; - do_check_arg_id(id); - } - FMT_CONSTEXPR void check_arg_id(basic_string_view) {} - FMT_CONSTEXPR void check_dynamic_spec(int arg_id); - - FMT_CONSTEXPR void on_error(const char* message) { - ErrorHandler::on_error(message); - } - - constexpr auto error_handler() const -> ErrorHandler { return *this; } -}; - -using format_parse_context = basic_format_parse_context; - -FMT_BEGIN_DETAIL_NAMESPACE -// A parse context with extra data used only in compile-time checks. -template -class compile_parse_context - : public basic_format_parse_context { - private: - int num_args_; - const type* types_; - using base = basic_format_parse_context; - - public: - explicit FMT_CONSTEXPR compile_parse_context( - basic_string_view format_str, int num_args, const type* types, - ErrorHandler eh = {}, int next_arg_id = 0) - : base(format_str, eh, next_arg_id), num_args_(num_args), types_(types) {} - - constexpr auto num_args() const -> int { return num_args_; } - constexpr auto arg_type(int id) const -> type { return types_[id]; } - - FMT_CONSTEXPR auto next_arg_id() -> int { - int id = base::next_arg_id(); - if (id >= num_args_) this->on_error("argument not found"); - return id; - } - - FMT_CONSTEXPR void check_arg_id(int id) { - base::check_arg_id(id); - if (id >= num_args_) this->on_error("argument not found"); - } - using base::check_arg_id; - - FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { - if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) - this->on_error("width/precision is not integer"); - } -}; -FMT_END_DETAIL_NAMESPACE - -template -FMT_CONSTEXPR void -basic_format_parse_context::do_check_arg_id(int id) { - // Argument id is only checked at compile-time during parsing because - // formatting has its own validation. - if (detail::is_constant_evaluated() && FMT_GCC_VERSION >= 1200) { - using context = detail::compile_parse_context; - if (id >= static_cast(this)->num_args()) - on_error("argument not found"); - } -} - -template -FMT_CONSTEXPR void -basic_format_parse_context::check_dynamic_spec(int arg_id) { - if (detail::is_constant_evaluated()) { - using context = detail::compile_parse_context; - static_cast(this)->check_dynamic_spec(arg_id); - } -} - -template class basic_format_arg; -template class basic_format_args; -template class dynamic_format_arg_store; - -// A formatter for objects of type T. -template -struct formatter { - // A deleted default constructor indicates a disabled formatter. - formatter() = delete; -}; - -// Specifies if T has an enabled formatter specialization. A type can be -// formattable even if it doesn't have a formatter e.g. via a conversion. -template -using has_formatter = - std::is_constructible>; - -// Checks whether T is a container with contiguous storage. -template struct is_contiguous : std::false_type {}; -template -struct is_contiguous> : std::true_type {}; - -class appender; - -FMT_BEGIN_DETAIL_NAMESPACE - -template -constexpr auto has_const_formatter_impl(T*) - -> decltype(typename Context::template formatter_type().format( - std::declval(), std::declval()), - true) { - return true; -} -template -constexpr auto has_const_formatter_impl(...) -> bool { - return false; -} -template -constexpr auto has_const_formatter() -> bool { - return has_const_formatter_impl(static_cast(nullptr)); -} - -// Extracts a reference to the container from back_insert_iterator. -template -inline auto get_container(std::back_insert_iterator it) - -> Container& { - using base = std::back_insert_iterator; - struct accessor : base { - accessor(base b) : base(b) {} - using base::container; - }; - return *accessor(it).container; -} - -template -FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out) - -> OutputIt { - while (begin != end) *out++ = static_cast(*begin++); - return out; -} - -template , U>::value&& is_char::value)> -FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out) -> U* { - if (is_constant_evaluated()) return copy_str(begin, end, out); - auto size = to_unsigned(end - begin); - memcpy(out, begin, size * sizeof(U)); - return out + size; -} - -/** - \rst - A contiguous memory buffer with an optional growing ability. It is an internal - class and shouldn't be used directly, only via `~fmt::basic_memory_buffer`. - \endrst - */ -template class buffer { - private: - T* ptr_; - size_t size_; - size_t capacity_; - - protected: - // Don't initialize ptr_ since it is not accessed to save a few cycles. - FMT_MSC_WARNING(suppress : 26495) - buffer(size_t sz) noexcept : size_(sz), capacity_(sz) {} - - FMT_CONSTEXPR20 buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) noexcept - : ptr_(p), size_(sz), capacity_(cap) {} - - FMT_CONSTEXPR20 ~buffer() = default; - buffer(buffer&&) = default; - - /** Sets the buffer data and capacity. */ - FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { - ptr_ = buf_data; - capacity_ = buf_capacity; - } - - /** Increases the buffer capacity to hold at least *capacity* elements. */ - virtual FMT_CONSTEXPR20 void grow(size_t capacity) = 0; - - public: - using value_type = T; - using const_reference = const T&; - - buffer(const buffer&) = delete; - void operator=(const buffer&) = delete; - - auto begin() noexcept -> T* { return ptr_; } - auto end() noexcept -> T* { return ptr_ + size_; } - - auto begin() const noexcept -> const T* { return ptr_; } - auto end() const noexcept -> const T* { return ptr_ + size_; } - - /** Returns the size of this buffer. */ - constexpr auto size() const noexcept -> size_t { return size_; } - - /** Returns the capacity of this buffer. */ - constexpr auto capacity() const noexcept -> size_t { return capacity_; } - - /** Returns a pointer to the buffer data. */ - FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } - - /** Returns a pointer to the buffer data. */ - FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } - - /** Clears this buffer. */ - void clear() { size_ = 0; } - - // Tries resizing the buffer to contain *count* elements. If T is a POD type - // the new elements may not be initialized. - FMT_CONSTEXPR20 void try_resize(size_t count) { - try_reserve(count); - size_ = count <= capacity_ ? count : capacity_; - } - - // Tries increasing the buffer capacity to *new_capacity*. It can increase the - // capacity by a smaller amount than requested but guarantees there is space - // for at least one additional element either by increasing the capacity or by - // flushing the buffer if it is full. - FMT_CONSTEXPR20 void try_reserve(size_t new_capacity) { - if (new_capacity > capacity_) grow(new_capacity); - } - - FMT_CONSTEXPR20 void push_back(const T& value) { - try_reserve(size_ + 1); - ptr_[size_++] = value; - } - - /** Appends data to the end of the buffer. */ - template void append(const U* begin, const U* end); - - template FMT_CONSTEXPR auto operator[](Idx index) -> T& { - return ptr_[index]; - } - template - FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { - return ptr_[index]; - } -}; - -struct buffer_traits { - explicit buffer_traits(size_t) {} - auto count() const -> size_t { return 0; } - auto limit(size_t size) -> size_t { return size; } -}; - -class fixed_buffer_traits { - private: - size_t count_ = 0; - size_t limit_; - - public: - explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} - auto count() const -> size_t { return count_; } - auto limit(size_t size) -> size_t { - size_t n = limit_ > count_ ? limit_ - count_ : 0; - count_ += size; - return size < n ? size : n; - } -}; - -// A buffer that writes to an output iterator when flushed. -template -class iterator_buffer final : public Traits, public buffer { - private: - OutputIt out_; - enum { buffer_size = 256 }; - T data_[buffer_size]; - - protected: - FMT_CONSTEXPR20 void grow(size_t) override { - if (this->size() == buffer_size) flush(); - } - - void flush() { - auto size = this->size(); - this->clear(); - out_ = copy_str(data_, data_ + this->limit(size), out_); - } - - public: - explicit iterator_buffer(OutputIt out, size_t n = buffer_size) - : Traits(n), buffer(data_, 0, buffer_size), out_(out) {} - iterator_buffer(iterator_buffer&& other) - : Traits(other), buffer(data_, 0, buffer_size), out_(other.out_) {} - ~iterator_buffer() { flush(); } - - auto out() -> OutputIt { - flush(); - return out_; - } - auto count() const -> size_t { return Traits::count() + this->size(); } -}; - -template -class iterator_buffer final - : public fixed_buffer_traits, - public buffer { - private: - T* out_; - enum { buffer_size = 256 }; - T data_[buffer_size]; - - protected: - FMT_CONSTEXPR20 void grow(size_t) override { - if (this->size() == this->capacity()) flush(); - } - - void flush() { - size_t n = this->limit(this->size()); - if (this->data() == out_) { - out_ += n; - this->set(data_, buffer_size); - } - this->clear(); - } - - public: - explicit iterator_buffer(T* out, size_t n = buffer_size) - : fixed_buffer_traits(n), buffer(out, 0, n), out_(out) {} - iterator_buffer(iterator_buffer&& other) - : fixed_buffer_traits(other), - buffer(std::move(other)), - out_(other.out_) { - if (this->data() != out_) { - this->set(data_, buffer_size); - this->clear(); - } - } - ~iterator_buffer() { flush(); } - - auto out() -> T* { - flush(); - return out_; - } - auto count() const -> size_t { - return fixed_buffer_traits::count() + this->size(); - } -}; - -template class iterator_buffer final : public buffer { - protected: - FMT_CONSTEXPR20 void grow(size_t) override {} - - public: - explicit iterator_buffer(T* out, size_t = 0) : buffer(out, 0, ~size_t()) {} - - auto out() -> T* { return &*this->end(); } -}; - -// A buffer that writes to a container with the contiguous storage. -template -class iterator_buffer, - enable_if_t::value, - typename Container::value_type>> - final : public buffer { - private: - Container& container_; - - protected: - FMT_CONSTEXPR20 void grow(size_t capacity) override { - container_.resize(capacity); - this->set(&container_[0], capacity); - } - - public: - explicit iterator_buffer(Container& c) - : buffer(c.size()), container_(c) {} - explicit iterator_buffer(std::back_insert_iterator out, size_t = 0) - : iterator_buffer(get_container(out)) {} - - auto out() -> std::back_insert_iterator { - return std::back_inserter(container_); - } -}; - -// A buffer that counts the number of code units written discarding the output. -template class counting_buffer final : public buffer { - private: - enum { buffer_size = 256 }; - T data_[buffer_size]; - size_t count_ = 0; - - protected: - FMT_CONSTEXPR20 void grow(size_t) override { - if (this->size() != buffer_size) return; - count_ += this->size(); - this->clear(); - } - - public: - counting_buffer() : buffer(data_, 0, buffer_size) {} - - auto count() -> size_t { return count_ + this->size(); } -}; - -template -using buffer_appender = conditional_t::value, appender, - std::back_insert_iterator>>; - -// Maps an output iterator to a buffer. -template -auto get_buffer(OutputIt out) -> iterator_buffer { - return iterator_buffer(out); -} - -template -auto get_iterator(Buffer& buf) -> decltype(buf.out()) { - return buf.out(); -} -template auto get_iterator(buffer& buf) -> buffer_appender { - return buffer_appender(buf); -} - -template -struct fallback_formatter { - fallback_formatter() = delete; -}; - -// Specifies if T has an enabled fallback_formatter specialization. -template -using has_fallback_formatter = -#ifdef FMT_DEPRECATED_OSTREAM - std::is_constructible>; -#else - std::false_type; -#endif - -struct view {}; - -template struct named_arg : view { - const Char* name; - const T& value; - named_arg(const Char* n, const T& v) : name(n), value(v) {} -}; - -template struct named_arg_info { - const Char* name; - int id; -}; - -template -struct arg_data { - // args_[0].named_args points to named_args_ to avoid bloating format_args. - // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. - T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)]; - named_arg_info named_args_[NUM_NAMED_ARGS]; - - template - arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {} - arg_data(const arg_data& other) = delete; - auto args() const -> const T* { return args_ + 1; } - auto named_args() -> named_arg_info* { return named_args_; } -}; - -template -struct arg_data { - // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. - T args_[NUM_ARGS != 0 ? NUM_ARGS : +1]; - - template - FMT_CONSTEXPR FMT_INLINE arg_data(const U&... init) : args_{init...} {} - FMT_CONSTEXPR FMT_INLINE auto args() const -> const T* { return args_; } - FMT_CONSTEXPR FMT_INLINE auto named_args() -> std::nullptr_t { - return nullptr; - } -}; - -template -inline void init_named_args(named_arg_info*, int, int) {} - -template struct is_named_arg : std::false_type {}; -template struct is_statically_named_arg : std::false_type {}; - -template -struct is_named_arg> : std::true_type {}; - -template ::value)> -void init_named_args(named_arg_info* named_args, int arg_count, - int named_arg_count, const T&, const Tail&... args) { - init_named_args(named_args, arg_count + 1, named_arg_count, args...); -} - -template ::value)> -void init_named_args(named_arg_info* named_args, int arg_count, - int named_arg_count, const T& arg, const Tail&... args) { - named_args[named_arg_count++] = {arg.name, arg_count}; - init_named_args(named_args, arg_count + 1, named_arg_count, args...); -} - -template -FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int, - const Args&...) {} - -template constexpr auto count() -> size_t { return B ? 1 : 0; } -template constexpr auto count() -> size_t { - return (B1 ? 1 : 0) + count(); -} - -template constexpr auto count_named_args() -> size_t { - return count::value...>(); -} - -template -constexpr auto count_statically_named_args() -> size_t { - return count::value...>(); -} - -struct unformattable {}; -struct unformattable_char : unformattable {}; -struct unformattable_const : unformattable {}; -struct unformattable_pointer : unformattable {}; - -template struct string_value { - const Char* data; - size_t size; -}; - -template struct named_arg_value { - const named_arg_info* data; - size_t size; -}; - -template struct custom_value { - using parse_context = typename Context::parse_context_type; - void* value; - void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); -}; - -// A formatting argument value. -template class value { - public: - using char_type = typename Context::char_type; - - union { - monostate no_value; - int int_value; - unsigned uint_value; - long long long_long_value; - unsigned long long ulong_long_value; - int128_opt int128_value; - uint128_opt uint128_value; - bool bool_value; - char_type char_value; - float float_value; - double double_value; - long double long_double_value; - const void* pointer; - string_value string; - custom_value custom; - named_arg_value named_args; - }; - - constexpr FMT_INLINE value() : no_value() {} - constexpr FMT_INLINE value(int val) : int_value(val) {} - constexpr FMT_INLINE value(unsigned val) : uint_value(val) {} - constexpr FMT_INLINE value(long long val) : long_long_value(val) {} - constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} - FMT_INLINE value(int128_opt val) : int128_value(val) {} - FMT_INLINE value(uint128_opt val) : uint128_value(val) {} - constexpr FMT_INLINE value(float val) : float_value(val) {} - constexpr FMT_INLINE value(double val) : double_value(val) {} - FMT_INLINE value(long double val) : long_double_value(val) {} - constexpr FMT_INLINE value(bool val) : bool_value(val) {} - constexpr FMT_INLINE value(char_type val) : char_value(val) {} - FMT_CONSTEXPR FMT_INLINE value(const char_type* val) { - string.data = val; - if (is_constant_evaluated()) string.size = {}; - } - FMT_CONSTEXPR FMT_INLINE value(basic_string_view val) { - string.data = val.data(); - string.size = val.size(); - } - FMT_INLINE value(const void* val) : pointer(val) {} - FMT_INLINE value(const named_arg_info* args, size_t size) - : named_args{args, size} {} - - template FMT_CONSTEXPR FMT_INLINE value(T& val) { - using value_type = remove_cvref_t; - custom.value = const_cast(&val); - // Get the formatter type through the context to allow different contexts - // have different extension points, e.g. `formatter` for `format` and - // `printf_formatter` for `printf`. - custom.format = format_custom_arg< - value_type, - conditional_t::value, - typename Context::template formatter_type, - fallback_formatter>>; - } - value(unformattable); - value(unformattable_char); - value(unformattable_const); - value(unformattable_pointer); - - private: - // Formats an argument of a custom type, such as a user-defined class. - template - static void format_custom_arg(void* arg, - typename Context::parse_context_type& parse_ctx, - Context& ctx) { - auto f = Formatter(); - parse_ctx.advance_to(f.parse(parse_ctx)); - using qualified_type = - conditional_t(), const T, T>; - ctx.advance_to(f.format(*static_cast(arg), ctx)); - } -}; - -template -FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg; - -// To minimize the number of types we need to deal with, long is translated -// either to int or to long long depending on its size. -enum { long_short = sizeof(long) == sizeof(int) }; -using long_type = conditional_t; -using ulong_type = conditional_t; - -#ifdef __cpp_lib_byte -inline auto format_as(std::byte b) -> unsigned char { - return static_cast(b); -} -#endif - -template struct has_format_as { - template ::value&& std::is_integral::value)> - static auto check(U*) -> std::true_type; - static auto check(...) -> std::false_type; - - enum { value = decltype(check(static_cast(nullptr)))::value }; -}; - -// Maps formatting arguments to core types. -// arg_mapper reports errors by returning unformattable instead of using -// static_assert because it's used in the is_formattable trait. -template struct arg_mapper { - using char_type = typename Context::char_type; - - FMT_CONSTEXPR FMT_INLINE auto map(signed char val) -> int { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned char val) -> unsigned { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(short val) -> int { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned short val) -> unsigned { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(int val) -> int { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned val) -> unsigned { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(long val) -> long_type { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned long val) -> ulong_type { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(long long val) -> long long { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned long long val) - -> unsigned long long { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(int128_opt val) -> int128_opt { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(uint128_opt val) -> uint128_opt { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(bool val) -> bool { return val; } - - template ::value || - std::is_same::value)> - FMT_CONSTEXPR FMT_INLINE auto map(T val) -> char_type { - return val; - } - template ::value || -#ifdef __cpp_char8_t - std::is_same::value || -#endif - std::is_same::value || - std::is_same::value) && - !std::is_same::value, - int> = 0> - FMT_CONSTEXPR FMT_INLINE auto map(T) -> unformattable_char { - return {}; - } - - FMT_CONSTEXPR FMT_INLINE auto map(float val) -> float { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(double val) -> double { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(long double val) -> long double { - return val; - } - - FMT_CONSTEXPR FMT_INLINE auto map(char_type* val) -> const char_type* { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(const char_type* val) -> const char_type* { - return val; - } - template ::value && !std::is_pointer::value && - std::is_same>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> basic_string_view { - return to_string_view(val); - } - template ::value && !std::is_pointer::value && - !std::is_same>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T&) -> unformattable_char { - return {}; - } - template >::value && - !is_string::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> basic_string_view { - return basic_string_view(val); - } - template >::value && - !std::is_convertible>::value && - !is_string::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> basic_string_view { - return std_string_view(val); - } - - FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(std::nullptr_t val) -> const void* { - return val; - } - - // We use SFINAE instead of a const T* parameter to avoid conflicting with - // the C array overload. - template < - typename T, - FMT_ENABLE_IF( - std::is_pointer::value || std::is_member_pointer::value || - std::is_function::type>::value || - (std::is_convertible::value && - !std::is_convertible::value && - !has_formatter::value))> - FMT_CONSTEXPR auto map(const T&) -> unformattable_pointer { - return {}; - } - - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] { - return values; - } - - template ::value&& std::is_convertible::value && - !has_format_as::value && !has_formatter::value && - !has_fallback_formatter::value)> - FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> decltype(std::declval().map( - static_cast>(val))) { - return map(static_cast>(val)); - } - - template ::value && - !has_formatter::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> decltype(std::declval().map(format_as(T()))) { - return map(format_as(val)); - } - - template > - struct formattable - : bool_constant() || - !std::is_const>::value || - has_fallback_formatter::value> {}; - -#if (FMT_MSC_VERSION != 0 && FMT_MSC_VERSION < 1910) || \ - FMT_ICC_VERSION != 0 || defined(__NVCC__) - // Workaround a bug in MSVC and Intel (Issue 2746). - template FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { - return val; - } -#else - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { - return val; - } - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable_const { - return {}; - } -#endif - - template , - FMT_ENABLE_IF(!is_string::value && !is_char::value && - !std::is_array::value && - !std::is_pointer::value && - !has_format_as::value && - (has_formatter::value || - has_fallback_formatter::value))> - FMT_CONSTEXPR FMT_INLINE auto map(T&& val) - -> decltype(this->do_map(std::forward(val))) { - return do_map(std::forward(val)); - } - - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) - -> decltype(std::declval().map(named_arg.value)) { - return map(named_arg.value); - } - - auto map(...) -> unformattable { return {}; } -}; - -// A type constant after applying arg_mapper. -template -using mapped_type_constant = - type_constant().map(std::declval())), - typename Context::char_type>; - -enum { packed_arg_bits = 4 }; -// Maximum number of arguments with packed types. -enum { max_packed_args = 62 / packed_arg_bits }; -enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; -enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; - -FMT_END_DETAIL_NAMESPACE - -// An output iterator that appends to a buffer. -// It is used to reduce symbol sizes for the common case. -class appender : public std::back_insert_iterator> { - using base = std::back_insert_iterator>; - - template - friend auto get_buffer(appender out) -> detail::buffer& { - return detail::get_container(out); - } - - public: - using std::back_insert_iterator>::back_insert_iterator; - appender(base it) noexcept : base(it) {} - FMT_UNCHECKED_ITERATOR(appender); - - auto operator++() noexcept -> appender& { return *this; } - auto operator++(int) noexcept -> appender { return *this; } -}; - -// A formatting argument. It is a trivially copyable/constructible type to -// allow storage in basic_memory_buffer. -template class basic_format_arg { - private: - detail::value value_; - detail::type type_; - - template - friend FMT_CONSTEXPR auto detail::make_arg(T&& value) - -> basic_format_arg; - - template - friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, - const basic_format_arg& arg) - -> decltype(vis(0)); - - friend class basic_format_args; - friend class dynamic_format_arg_store; - - using char_type = typename Context::char_type; - - template - friend struct detail::arg_data; - - basic_format_arg(const detail::named_arg_info* args, size_t size) - : value_(args, size) {} - - public: - class handle { - public: - explicit handle(detail::custom_value custom) : custom_(custom) {} - - void format(typename Context::parse_context_type& parse_ctx, - Context& ctx) const { - custom_.format(custom_.value, parse_ctx, ctx); - } - - private: - detail::custom_value custom_; - }; - - constexpr basic_format_arg() : type_(detail::type::none_type) {} - - constexpr explicit operator bool() const noexcept { - return type_ != detail::type::none_type; - } - - auto type() const -> detail::type { return type_; } - - auto is_integral() const -> bool { return detail::is_integral_type(type_); } - auto is_arithmetic() const -> bool { - return detail::is_arithmetic_type(type_); - } -}; - -/** - \rst - Visits an argument dispatching to the appropriate visit method based on - the argument type. For example, if the argument type is ``double`` then - ``vis(value)`` will be called with the value of type ``double``. - \endrst - */ -template -FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( - Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { - switch (arg.type_) { - case detail::type::none_type: - break; - case detail::type::int_type: - return vis(arg.value_.int_value); - case detail::type::uint_type: - return vis(arg.value_.uint_value); - case detail::type::long_long_type: - return vis(arg.value_.long_long_value); - case detail::type::ulong_long_type: - return vis(arg.value_.ulong_long_value); - case detail::type::int128_type: - return vis(detail::convert_for_visit(arg.value_.int128_value)); - case detail::type::uint128_type: - return vis(detail::convert_for_visit(arg.value_.uint128_value)); - case detail::type::bool_type: - return vis(arg.value_.bool_value); - case detail::type::char_type: - return vis(arg.value_.char_value); - case detail::type::float_type: - return vis(arg.value_.float_value); - case detail::type::double_type: - return vis(arg.value_.double_value); - case detail::type::long_double_type: - return vis(arg.value_.long_double_value); - case detail::type::cstring_type: - return vis(arg.value_.string.data); - case detail::type::string_type: - using sv = basic_string_view; - return vis(sv(arg.value_.string.data, arg.value_.string.size)); - case detail::type::pointer_type: - return vis(arg.value_.pointer); - case detail::type::custom_type: - return vis(typename basic_format_arg::handle(arg.value_.custom)); - } - return vis(monostate()); -} - -FMT_BEGIN_DETAIL_NAMESPACE - -template -auto copy_str(InputIt begin, InputIt end, appender out) -> appender { - get_container(out).append(begin, end); - return out; -} - -template -FMT_CONSTEXPR auto copy_str(R&& rng, OutputIt out) -> OutputIt { - return detail::copy_str(rng.begin(), rng.end(), out); -} - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 -// A workaround for gcc 4.8 to make void_t work in a SFINAE context. -template struct void_t_impl { using type = void; }; -template -using void_t = typename detail::void_t_impl::type; -#else -template using void_t = void; -#endif - -template -struct is_output_iterator : std::false_type {}; - -template -struct is_output_iterator< - It, T, - void_t::iterator_category, - decltype(*std::declval() = std::declval())>> - : std::true_type {}; - -template -struct is_back_insert_iterator : std::false_type {}; -template -struct is_back_insert_iterator> - : std::true_type {}; - -template -struct is_contiguous_back_insert_iterator : std::false_type {}; -template -struct is_contiguous_back_insert_iterator> - : is_contiguous {}; -template <> -struct is_contiguous_back_insert_iterator : std::true_type {}; - -// A type-erased reference to an std::locale to avoid a heavy include. -class locale_ref { - private: - const void* locale_; // A type-erased pointer to std::locale. - - public: - constexpr locale_ref() : locale_(nullptr) {} - template explicit locale_ref(const Locale& loc); - - explicit operator bool() const noexcept { return locale_ != nullptr; } - - template auto get() const -> Locale; -}; - -template constexpr auto encode_types() -> unsigned long long { - return 0; -} - -template -constexpr auto encode_types() -> unsigned long long { - return static_cast(mapped_type_constant::value) | - (encode_types() << packed_arg_bits); -} - -template -FMT_CONSTEXPR FMT_INLINE auto make_value(T&& val) -> value { - const auto& arg = arg_mapper().map(FMT_FORWARD(val)); - - constexpr bool formattable_char = - !std::is_same::value; - static_assert(formattable_char, "Mixing character types is disallowed."); - - constexpr bool formattable_const = - !std::is_same::value; - static_assert(formattable_const, "Cannot format a const argument."); - - // Formatting of arbitrary pointers is disallowed. If you want to output - // a pointer cast it to "void *" or "const void *". In particular, this - // forbids formatting of "[const] volatile char *" which is printed as bool - // by iostreams. - constexpr bool formattable_pointer = - !std::is_same::value; - static_assert(formattable_pointer, - "Formatting of non-void pointers is disallowed."); - - constexpr bool formattable = - !std::is_same::value; - static_assert( - formattable, - "Cannot format an argument. To make type T formattable provide a " - "formatter specialization: https://fmt.dev/latest/api.html#udt"); - return {arg}; -} - -template -FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg { - basic_format_arg arg; - arg.type_ = mapped_type_constant::value; - arg.value_ = make_value(value); - return arg; -} - -// The type template parameter is there to avoid an ODR violation when using -// a fallback formatter in one translation unit and an implicit conversion in -// another (not recommended). -template -FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value { - return make_value(val); -} - -template -FMT_CONSTEXPR inline auto make_arg(T&& value) -> basic_format_arg { - return make_arg(value); -} -FMT_END_DETAIL_NAMESPACE - -// Formatting context. -template class basic_format_context { - public: - /** The character type for the output. */ - using char_type = Char; - - private: - OutputIt out_; - basic_format_args args_; - detail::locale_ref loc_; - - public: - using iterator = OutputIt; - using format_arg = basic_format_arg; - using parse_context_type = basic_format_parse_context; - template using formatter_type = formatter; - - basic_format_context(basic_format_context&&) = default; - basic_format_context(const basic_format_context&) = delete; - void operator=(const basic_format_context&) = delete; - /** - Constructs a ``basic_format_context`` object. References to the arguments are - stored in the object so make sure they have appropriate lifetimes. - */ - constexpr basic_format_context( - OutputIt out, basic_format_args ctx_args, - detail::locale_ref loc = detail::locale_ref()) - : out_(out), args_(ctx_args), loc_(loc) {} - - constexpr auto arg(int id) const -> format_arg { return args_.get(id); } - FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { - return args_.get(name); - } - FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { - return args_.get_id(name); - } - auto args() const -> const basic_format_args& { - return args_; - } - - FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } - void on_error(const char* message) { error_handler().on_error(message); } - - // Returns an iterator to the beginning of the output range. - FMT_CONSTEXPR auto out() -> iterator { return out_; } - - // Advances the begin iterator to ``it``. - void advance_to(iterator it) { - if (!detail::is_back_insert_iterator()) out_ = it; - } - - FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } -}; - -template -using buffer_context = - basic_format_context, Char>; -using format_context = buffer_context; - -// Workaround an alias issue: https://stackoverflow.com/q/62767544/471164. -#define FMT_BUFFER_CONTEXT(Char) \ - basic_format_context, Char> - -template -using is_formattable = bool_constant< - !std::is_base_of>().map( - std::declval()))>::value && - !detail::has_fallback_formatter::value>; - -/** - \rst - An array of references to arguments. It can be implicitly converted into - `~fmt::basic_format_args` for passing into type-erased formatting functions - such as `~fmt::vformat`. - \endrst - */ -template -class format_arg_store -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args -#endif -{ - private: - static const size_t num_args = sizeof...(Args); - static const size_t num_named_args = detail::count_named_args(); - static const bool is_packed = num_args <= detail::max_packed_args; - - using value_type = conditional_t, - basic_format_arg>; - - detail::arg_data - data_; - - friend class basic_format_args; - - static constexpr unsigned long long desc = - (is_packed ? detail::encode_types() - : detail::is_unpacked_bit | num_args) | - (num_named_args != 0 - ? static_cast(detail::has_named_args_bit) - : 0); - - public: - template - FMT_CONSTEXPR FMT_INLINE format_arg_store(T&&... args) - : -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - basic_format_args(*this), -#endif - data_{detail::make_arg< - is_packed, Context, - detail::mapped_type_constant, Context>::value>( - FMT_FORWARD(args))...} { - detail::init_named_args(data_.named_args(), 0, 0, args...); - } -}; - -/** - \rst - Constructs a `~fmt::format_arg_store` object that contains references to - arguments and can be implicitly converted to `~fmt::format_args`. `Context` - can be omitted in which case it defaults to `~fmt::context`. - See `~fmt::arg` for lifetime considerations. - \endrst - */ -template -constexpr auto make_format_args(Args&&... args) - -> format_arg_store...> { - return {FMT_FORWARD(args)...}; -} - -/** - \rst - Returns a named argument to be used in a formatting function. - It should only be used in a call to a formatting function or - `dynamic_format_arg_store::push_back`. - - **Example**:: - - fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); - \endrst - */ -template -inline auto arg(const Char* name, const T& arg) -> detail::named_arg { - static_assert(!detail::is_named_arg(), "nested named arguments"); - return {name, arg}; -} - -/** - \rst - A view of a collection of formatting arguments. To avoid lifetime issues it - should only be used as a parameter type in type-erased functions such as - ``vformat``:: - - void vlog(string_view format_str, format_args args); // OK - format_args args = make_format_args(42); // Error: dangling reference - \endrst - */ -template class basic_format_args { - public: - using size_type = int; - using format_arg = basic_format_arg; - - private: - // A descriptor that contains information about formatting arguments. - // If the number of arguments is less or equal to max_packed_args then - // argument types are passed in the descriptor. This reduces binary code size - // per formatting function call. - unsigned long long desc_; - union { - // If is_packed() returns true then argument values are stored in values_; - // otherwise they are stored in args_. This is done to improve cache - // locality and reduce compiled code size since storing larger objects - // may require more code (at least on x86-64) even if the same amount of - // data is actually copied to stack. It saves ~10% on the bloat test. - const detail::value* values_; - const format_arg* args_; - }; - - constexpr auto is_packed() const -> bool { - return (desc_ & detail::is_unpacked_bit) == 0; - } - auto has_named_args() const -> bool { - return (desc_ & detail::has_named_args_bit) != 0; - } - - FMT_CONSTEXPR auto type(int index) const -> detail::type { - int shift = index * detail::packed_arg_bits; - unsigned int mask = (1 << detail::packed_arg_bits) - 1; - return static_cast((desc_ >> shift) & mask); - } - - constexpr FMT_INLINE basic_format_args(unsigned long long desc, - const detail::value* values) - : desc_(desc), values_(values) {} - constexpr basic_format_args(unsigned long long desc, const format_arg* args) - : desc_(desc), args_(args) {} - - public: - constexpr basic_format_args() : desc_(0), args_(nullptr) {} - - /** - \rst - Constructs a `basic_format_args` object from `~fmt::format_arg_store`. - \endrst - */ - template - constexpr FMT_INLINE basic_format_args( - const format_arg_store& store) - : basic_format_args(format_arg_store::desc, - store.data_.args()) {} - - /** - \rst - Constructs a `basic_format_args` object from - `~fmt::dynamic_format_arg_store`. - \endrst - */ - constexpr FMT_INLINE basic_format_args( - const dynamic_format_arg_store& store) - : basic_format_args(store.get_types(), store.data()) {} - - /** - \rst - Constructs a `basic_format_args` object from a dynamic set of arguments. - \endrst - */ - constexpr basic_format_args(const format_arg* args, int count) - : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), - args) {} - - /** Returns the argument with the specified id. */ - FMT_CONSTEXPR auto get(int id) const -> format_arg { - format_arg arg; - if (!is_packed()) { - if (id < max_size()) arg = args_[id]; - return arg; - } - if (id >= detail::max_packed_args) return arg; - arg.type_ = type(id); - if (arg.type_ == detail::type::none_type) return arg; - arg.value_ = values_[id]; - return arg; - } - - template - auto get(basic_string_view name) const -> format_arg { - int id = get_id(name); - return id >= 0 ? get(id) : format_arg(); - } - - template - auto get_id(basic_string_view name) const -> int { - if (!has_named_args()) return -1; - const auto& named_args = - (is_packed() ? values_[-1] : args_[-1].value_).named_args; - for (size_t i = 0; i < named_args.size; ++i) { - if (named_args.data[i].name == name) return named_args.data[i].id; - } - return -1; - } - - auto max_size() const -> int { - unsigned long long max_packed = detail::max_packed_args; - return static_cast(is_packed() ? max_packed - : desc_ & ~detail::is_unpacked_bit); - } -}; - -/** An alias to ``basic_format_args``. */ -// A separate type would result in shorter symbols but break ABI compatibility -// between clang and gcc on ARM (#1919). -using format_args = basic_format_args; - -// We cannot use enum classes as bit fields because of a gcc bug, so we put them -// in namespaces instead (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414). -// Additionally, if an underlying type is specified, older gcc incorrectly warns -// that the type is too small. Both bugs are fixed in gcc 9.3. -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 903 -# define FMT_ENUM_UNDERLYING_TYPE(type) -#else -# define FMT_ENUM_UNDERLYING_TYPE(type) : type -#endif -namespace align { -enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, left, right, center, - numeric}; -} -using align_t = align::type; -namespace sign { -enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, minus, plus, space}; -} -using sign_t = sign::type; - -FMT_BEGIN_DETAIL_NAMESPACE - -// Workaround an array initialization issue in gcc 4.8. -template struct fill_t { - private: - enum { max_size = 4 }; - Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)}; - unsigned char size_ = 1; - - public: - FMT_CONSTEXPR void operator=(basic_string_view s) { - auto size = s.size(); - if (size > max_size) return throw_format_error("invalid fill"); - for (size_t i = 0; i < size; ++i) data_[i] = s[i]; - size_ = static_cast(size); - } - - constexpr auto size() const -> size_t { return size_; } - constexpr auto data() const -> const Char* { return data_; } - - FMT_CONSTEXPR auto operator[](size_t index) -> Char& { return data_[index]; } - FMT_CONSTEXPR auto operator[](size_t index) const -> const Char& { - return data_[index]; - } -}; -FMT_END_DETAIL_NAMESPACE - -enum class presentation_type : unsigned char { - none, - // Integer types should go first, - dec, // 'd' - oct, // 'o' - hex_lower, // 'x' - hex_upper, // 'X' - bin_lower, // 'b' - bin_upper, // 'B' - hexfloat_lower, // 'a' - hexfloat_upper, // 'A' - exp_lower, // 'e' - exp_upper, // 'E' - fixed_lower, // 'f' - fixed_upper, // 'F' - general_lower, // 'g' - general_upper, // 'G' - chr, // 'c' - string, // 's' - pointer, // 'p' - debug // '?' -}; - -// Format specifiers for built-in and string types. -template struct basic_format_specs { - int width; - int precision; - presentation_type type; - align_t align : 4; - sign_t sign : 3; - bool alt : 1; // Alternate form ('#'). - bool localized : 1; - detail::fill_t fill; - - constexpr basic_format_specs() - : width(0), - precision(-1), - type(presentation_type::none), - align(align::none), - sign(sign::none), - alt(false), - localized(false) {} -}; - -using format_specs = basic_format_specs; - -FMT_BEGIN_DETAIL_NAMESPACE - -enum class arg_id_kind { none, index, name }; - -// An argument reference. -template struct arg_ref { - FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} - - FMT_CONSTEXPR explicit arg_ref(int index) - : kind(arg_id_kind::index), val(index) {} - FMT_CONSTEXPR explicit arg_ref(basic_string_view name) - : kind(arg_id_kind::name), val(name) {} - - FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& { - kind = arg_id_kind::index; - val.index = idx; - return *this; - } - - arg_id_kind kind; - union value { - FMT_CONSTEXPR value(int id = 0) : index{id} {} - FMT_CONSTEXPR value(basic_string_view n) : name(n) {} - - int index; - basic_string_view name; - } val; -}; - -// Format specifiers with width and precision resolved at formatting rather -// than parsing time to allow re-using the same parsed specifiers with -// different sets of arguments (precompilation of format strings). -template -struct dynamic_format_specs : basic_format_specs { - arg_ref width_ref; - arg_ref precision_ref; -}; - -struct auto_id {}; - -// A format specifier handler that sets fields in basic_format_specs. -template class specs_setter { - protected: - basic_format_specs& specs_; - - public: - explicit FMT_CONSTEXPR specs_setter(basic_format_specs& specs) - : specs_(specs) {} - - FMT_CONSTEXPR specs_setter(const specs_setter& other) - : specs_(other.specs_) {} - - FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } - FMT_CONSTEXPR void on_fill(basic_string_view fill) { - specs_.fill = fill; - } - FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; } - FMT_CONSTEXPR void on_hash() { specs_.alt = true; } - FMT_CONSTEXPR void on_localized() { specs_.localized = true; } - - FMT_CONSTEXPR void on_zero() { - if (specs_.align == align::none) specs_.align = align::numeric; - specs_.fill[0] = Char('0'); - } - - FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } - FMT_CONSTEXPR void on_precision(int precision) { - specs_.precision = precision; - } - FMT_CONSTEXPR void end_precision() {} - - FMT_CONSTEXPR void on_type(presentation_type type) { specs_.type = type; } -}; - -// Format spec handler that saves references to arguments representing dynamic -// width and precision to be resolved at formatting time. -template -class dynamic_specs_handler - : public specs_setter { - public: - using char_type = typename ParseContext::char_type; - - FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs& specs, - ParseContext& ctx) - : specs_setter(specs), specs_(specs), context_(ctx) {} - - FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other) - : specs_setter(other), - specs_(other.specs_), - context_(other.context_) {} - - template FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - specs_.width_ref = make_arg_ref(arg_id); - } - - template FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - specs_.precision_ref = make_arg_ref(arg_id); - } - - FMT_CONSTEXPR void on_error(const char* message) { - context_.on_error(message); - } - - private: - dynamic_format_specs& specs_; - ParseContext& context_; - - using arg_ref_type = arg_ref; - - FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type { - context_.check_arg_id(arg_id); - context_.check_dynamic_spec(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type { - int arg_id = context_.next_arg_id(); - context_.check_dynamic_spec(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR auto make_arg_ref(basic_string_view arg_id) - -> arg_ref_type { - context_.check_arg_id(arg_id); - basic_string_view format_str( - context_.begin(), to_unsigned(context_.end() - context_.begin())); - return arg_ref_type(arg_id); - } -}; - -template constexpr bool is_ascii_letter(Char c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); -} - -// Converts a character to ASCII. Returns a number > 127 on conversion failure. -template ::value)> -constexpr auto to_ascii(Char c) -> Char { - return c; -} -template ::value)> -constexpr auto to_ascii(Char c) -> underlying_t { - return c; -} - -FMT_CONSTEXPR inline auto code_point_length_impl(char c) -> int { - return "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" - [static_cast(c) >> 3]; -} - -template -FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { - if (const_check(sizeof(Char) != 1)) return 1; - int len = code_point_length_impl(static_cast(*begin)); - - // Compute the pointer to the next character early so that the next - // iteration can start working on the next character. Neither Clang - // nor GCC figure out this reordering on their own. - return len + !len; -} - -// Return the result via the out param to workaround gcc bug 77539. -template -FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { - for (out = first; out != last; ++out) { - if (*out == value) return true; - } - return false; -} - -template <> -inline auto find(const char* first, const char* last, char value, - const char*& out) -> bool { - out = static_cast( - std::memchr(first, value, to_unsigned(last - first))); - return out != nullptr; -} - -// Parses the range [begin, end) as an unsigned integer. This function assumes -// that the range is non-empty and the first character is a digit. -template -FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, - int error_value) noexcept -> int { - FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); - unsigned value = 0, prev = 0; - auto p = begin; - do { - prev = value; - value = value * 10 + unsigned(*p - '0'); - ++p; - } while (p != end && '0' <= *p && *p <= '9'); - auto num_digits = p - begin; - begin = p; - if (num_digits <= std::numeric_limits::digits10) - return static_cast(value); - // Check for overflow. - const unsigned max = to_unsigned((std::numeric_limits::max)()); - return num_digits == std::numeric_limits::digits10 + 1 && - prev * 10ull + unsigned(p[-1] - '0') <= max - ? static_cast(value) - : error_value; -} - -// Parses fill and alignment. -template -FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - FMT_ASSERT(begin != end, ""); - auto align = align::none; - auto p = begin + code_point_length(begin); - if (end - p <= 0) p = begin; - for (;;) { - switch (to_ascii(*p)) { - case '<': - align = align::left; - break; - case '>': - align = align::right; - break; - case '^': - align = align::center; - break; - default: - break; - } - if (align != align::none) { - if (p != begin) { - auto c = *begin; - if (c == '{') - return handler.on_error("invalid fill character '{'"), begin; - handler.on_fill(basic_string_view(begin, to_unsigned(p - begin))); - begin = p + 1; - } else - ++begin; - handler.on_align(align); - break; - } else if (p == begin) { - break; - } - p = begin; - } - return begin; -} - -template FMT_CONSTEXPR bool is_name_start(Char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; -} - -template -FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, - IDHandler&& handler) -> const Char* { - FMT_ASSERT(begin != end, ""); - Char c = *begin; - if (c >= '0' && c <= '9') { - int index = 0; - if (c != '0') - index = - parse_nonnegative_int(begin, end, (std::numeric_limits::max)()); - else - ++begin; - if (begin == end || (*begin != '}' && *begin != ':')) - handler.on_error("invalid format string"); - else - handler(index); - return begin; - } - if (!is_name_start(c)) { - handler.on_error("invalid format string"); - return begin; - } - auto it = begin; - do { - ++it; - } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9'))); - handler(basic_string_view(begin, to_unsigned(it - begin))); - return it; -} - -template -FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end, - IDHandler&& handler) -> const Char* { - Char c = *begin; - if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); - handler(); - return begin; -} - -template -FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - using detail::auto_id; - struct width_adapter { - Handler& handler; - - FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } - FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); } - FMT_CONSTEXPR void operator()(basic_string_view id) { - handler.on_dynamic_width(id); - } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } - }; - - FMT_ASSERT(begin != end, ""); - if ('0' <= *begin && *begin <= '9') { - int width = parse_nonnegative_int(begin, end, -1); - if (width != -1) - handler.on_width(width); - else - handler.on_error("number is too big"); - } else if (*begin == '{') { - ++begin; - if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler}); - if (begin == end || *begin != '}') - return handler.on_error("invalid format string"), begin; - ++begin; - } - return begin; -} - -template -FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - using detail::auto_id; - struct precision_adapter { - Handler& handler; - - FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } - FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); } - FMT_CONSTEXPR void operator()(basic_string_view id) { - handler.on_dynamic_precision(id); - } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } - }; - - ++begin; - auto c = begin != end ? *begin : Char(); - if ('0' <= c && c <= '9') { - auto precision = parse_nonnegative_int(begin, end, -1); - if (precision != -1) - handler.on_precision(precision); - else - handler.on_error("number is too big"); - } else if (c == '{') { - ++begin; - if (begin != end) - begin = parse_arg_id(begin, end, precision_adapter{handler}); - if (begin == end || *begin++ != '}') - return handler.on_error("invalid format string"), begin; - } else { - return handler.on_error("missing precision specifier"), begin; - } - handler.end_precision(); - return begin; -} - -template -FMT_CONSTEXPR auto parse_presentation_type(Char type) -> presentation_type { - switch (to_ascii(type)) { - case 'd': - return presentation_type::dec; - case 'o': - return presentation_type::oct; - case 'x': - return presentation_type::hex_lower; - case 'X': - return presentation_type::hex_upper; - case 'b': - return presentation_type::bin_lower; - case 'B': - return presentation_type::bin_upper; - case 'a': - return presentation_type::hexfloat_lower; - case 'A': - return presentation_type::hexfloat_upper; - case 'e': - return presentation_type::exp_lower; - case 'E': - return presentation_type::exp_upper; - case 'f': - return presentation_type::fixed_lower; - case 'F': - return presentation_type::fixed_upper; - case 'g': - return presentation_type::general_lower; - case 'G': - return presentation_type::general_upper; - case 'c': - return presentation_type::chr; - case 's': - return presentation_type::string; - case 'p': - return presentation_type::pointer; - case '?': - return presentation_type::debug; - default: - return presentation_type::none; - } -} - -// Parses standard format specifiers and sends notifications about parsed -// components to handler. -template -FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, - const Char* end, - SpecHandler&& handler) - -> const Char* { - if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) && - *begin != 'L') { - presentation_type type = parse_presentation_type(*begin++); - if (type == presentation_type::none) - handler.on_error("invalid type specifier"); - handler.on_type(type); - return begin; - } - - if (begin == end) return begin; - - begin = parse_align(begin, end, handler); - if (begin == end) return begin; - - // Parse sign. - switch (to_ascii(*begin)) { - case '+': - handler.on_sign(sign::plus); - ++begin; - break; - case '-': - handler.on_sign(sign::minus); - ++begin; - break; - case ' ': - handler.on_sign(sign::space); - ++begin; - break; - default: - break; - } - if (begin == end) return begin; - - if (*begin == '#') { - handler.on_hash(); - if (++begin == end) return begin; - } - - // Parse zero flag. - if (*begin == '0') { - handler.on_zero(); - if (++begin == end) return begin; - } - - begin = parse_width(begin, end, handler); - if (begin == end) return begin; - - // Parse precision. - if (*begin == '.') { - begin = parse_precision(begin, end, handler); - if (begin == end) return begin; - } - - if (*begin == 'L') { - handler.on_localized(); - ++begin; - } - - // Parse type. - if (begin != end && *begin != '}') { - presentation_type type = parse_presentation_type(*begin++); - if (type == presentation_type::none) - handler.on_error("invalid type specifier"); - handler.on_type(type); - } - return begin; -} - -template -FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - struct id_adapter { - Handler& handler; - int arg_id; - - FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); } - FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); } - FMT_CONSTEXPR void operator()(basic_string_view id) { - arg_id = handler.on_arg_id(id); - } - FMT_CONSTEXPR void on_error(const char* message) { - if (message) handler.on_error(message); - } - }; - - ++begin; - if (begin == end) return handler.on_error("invalid format string"), end; - if (*begin == '}') { - handler.on_replacement_field(handler.on_arg_id(), begin); - } else if (*begin == '{') { - handler.on_text(begin, begin + 1); - } else { - auto adapter = id_adapter{handler, 0}; - begin = parse_arg_id(begin, end, adapter); - Char c = begin != end ? *begin : Char(); - if (c == '}') { - handler.on_replacement_field(adapter.arg_id, begin); - } else if (c == ':') { - begin = handler.on_format_specs(adapter.arg_id, begin + 1, end); - if (begin == end || *begin != '}') - return handler.on_error("unknown format specifier"), end; - } else { - return handler.on_error("missing '}' in format string"), end; - } - } - return begin + 1; -} - -template -FMT_CONSTEXPR FMT_INLINE void parse_format_string( - basic_string_view format_str, Handler&& handler) { - // Workaround a name-lookup bug in MSVC's modules implementation. - using detail::find; - - auto begin = format_str.data(); - auto end = begin + format_str.size(); - if (end - begin < 32) { - // Use a simple loop instead of memchr for small strings. - const Char* p = begin; - while (p != end) { - auto c = *p++; - if (c == '{') { - handler.on_text(begin, p - 1); - begin = p = parse_replacement_field(p - 1, end, handler); - } else if (c == '}') { - if (p == end || *p != '}') - return handler.on_error("unmatched '}' in format string"); - handler.on_text(begin, p); - begin = ++p; - } - } - handler.on_text(begin, end); - return; - } - struct writer { - FMT_CONSTEXPR void operator()(const Char* from, const Char* to) { - if (from == to) return; - for (;;) { - const Char* p = nullptr; - if (!find(from, to, Char('}'), p)) - return handler_.on_text(from, to); - ++p; - if (p == to || *p != '}') - return handler_.on_error("unmatched '}' in format string"); - handler_.on_text(from, p); - from = p + 1; - } - } - Handler& handler_; - } write = {handler}; - while (begin != end) { - // Doing two passes with memchr (one for '{' and another for '}') is up to - // 2.5x faster than the naive one-pass implementation on big format strings. - const Char* p = begin; - if (*begin != '{' && !find(begin + 1, end, Char('{'), p)) - return write(begin, end); - write(begin, p); - begin = parse_replacement_field(p, end, handler); - } -} - -template ::value> struct strip_named_arg { - using type = T; -}; -template struct strip_named_arg { - using type = remove_cvref_t; -}; - -template -FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) - -> decltype(ctx.begin()) { - using char_type = typename ParseContext::char_type; - using context = buffer_context; - using stripped_type = typename strip_named_arg::type; - using mapped_type = conditional_t< - mapped_type_constant::value != type::custom_type, - decltype(arg_mapper().map(std::declval())), - stripped_type>; - auto f = conditional_t::value, - formatter, - fallback_formatter>(); - return f.parse(ctx); -} - -template -FMT_CONSTEXPR void check_int_type_spec(presentation_type type, - ErrorHandler&& eh) { - if (type > presentation_type::bin_upper && type != presentation_type::chr) - eh.on_error("invalid type specifier"); -} - -// Checks char specs and returns true if the type spec is char (and not int). -template -FMT_CONSTEXPR auto check_char_specs(const basic_format_specs& specs, - ErrorHandler&& eh = {}) -> bool { - if (specs.type != presentation_type::none && - specs.type != presentation_type::chr && - specs.type != presentation_type::debug) { - check_int_type_spec(specs.type, eh); - return false; - } - if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) - eh.on_error("invalid format specifier for char"); - return true; -} - -// A floating-point presentation format. -enum class float_format : unsigned char { - general, // General: exponent notation or fixed point based on magnitude. - exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. - fixed, // Fixed point with the default precision of 6, e.g. 0.0012. - hex -}; - -struct float_specs { - int precision; - float_format format : 8; - sign_t sign : 8; - bool upper : 1; - bool locale : 1; - bool binary32 : 1; - bool showpoint : 1; -}; - -template -FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs& specs, - ErrorHandler&& eh = {}) - -> float_specs { - auto result = float_specs(); - result.showpoint = specs.alt; - result.locale = specs.localized; - switch (specs.type) { - case presentation_type::none: - result.format = float_format::general; - break; - case presentation_type::general_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::general_lower: - result.format = float_format::general; - break; - case presentation_type::exp_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::exp_lower: - result.format = float_format::exp; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::fixed_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::fixed_lower: - result.format = float_format::fixed; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::hexfloat_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::hexfloat_lower: - result.format = float_format::hex; - break; - default: - eh.on_error("invalid type specifier"); - break; - } - return result; -} - -template -FMT_CONSTEXPR auto check_cstring_type_spec(presentation_type type, - ErrorHandler&& eh = {}) -> bool { - if (type == presentation_type::none || type == presentation_type::string || - type == presentation_type::debug) - return true; - if (type != presentation_type::pointer) eh.on_error("invalid type specifier"); - return false; -} - -template -FMT_CONSTEXPR void check_string_type_spec(presentation_type type, - ErrorHandler&& eh = {}) { - if (type != presentation_type::none && type != presentation_type::string && - type != presentation_type::debug) - eh.on_error("invalid type specifier"); -} - -template -FMT_CONSTEXPR void check_pointer_type_spec(presentation_type type, - ErrorHandler&& eh) { - if (type != presentation_type::none && type != presentation_type::pointer) - eh.on_error("invalid type specifier"); -} - -// A parse_format_specs handler that checks if specifiers are consistent with -// the argument type. -template class specs_checker : public Handler { - private: - detail::type arg_type_; - - FMT_CONSTEXPR void require_numeric_argument() { - if (!is_arithmetic_type(arg_type_)) - this->on_error("format specifier requires numeric argument"); - } - - public: - FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) - : Handler(handler), arg_type_(arg_type) {} - - FMT_CONSTEXPR void on_align(align_t align) { - if (align == align::numeric) require_numeric_argument(); - Handler::on_align(align); - } - - FMT_CONSTEXPR void on_sign(sign_t s) { - require_numeric_argument(); - if (is_integral_type(arg_type_) && arg_type_ != type::int_type && - arg_type_ != type::long_long_type && arg_type_ != type::int128_type && - arg_type_ != type::char_type) { - this->on_error("format specifier requires signed argument"); - } - Handler::on_sign(s); - } - - FMT_CONSTEXPR void on_hash() { - require_numeric_argument(); - Handler::on_hash(); - } - - FMT_CONSTEXPR void on_localized() { - require_numeric_argument(); - Handler::on_localized(); - } - - FMT_CONSTEXPR void on_zero() { - require_numeric_argument(); - Handler::on_zero(); - } - - FMT_CONSTEXPR void end_precision() { - if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) - this->on_error("precision not allowed for this argument type"); - } -}; - -constexpr int invalid_arg_index = -1; - -#if FMT_USE_NONTYPE_TEMPLATE_ARGS -template -constexpr auto get_arg_index_by_name(basic_string_view name) -> int { - if constexpr (detail::is_statically_named_arg()) { - if (name == T::name) return N; - } - if constexpr (sizeof...(Args) > 0) - return get_arg_index_by_name(name); - (void)name; // Workaround an MSVC bug about "unused" parameter. - return invalid_arg_index; -} -#endif - -template -FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { -#if FMT_USE_NONTYPE_TEMPLATE_ARGS - if constexpr (sizeof...(Args) > 0) - return get_arg_index_by_name<0, Args...>(name); -#endif - (void)name; - return invalid_arg_index; -} - -template -class format_string_checker { - private: - // In the future basic_format_parse_context will replace compile_parse_context - // here and will use is_constant_evaluated and downcasting to access the data - // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. - using parse_context_type = compile_parse_context; - static constexpr int num_args = sizeof...(Args); - - // Format specifier parsing function. - using parse_func = const Char* (*)(parse_context_type&); - - parse_context_type context_; - parse_func parse_funcs_[num_args > 0 ? static_cast(num_args) : 1]; - type types_[num_args > 0 ? static_cast(num_args) : 1]; - - public: - explicit FMT_CONSTEXPR format_string_checker( - basic_string_view format_str, ErrorHandler eh) - : context_(format_str, num_args, types_, eh), - parse_funcs_{&parse_format_specs...}, - types_{ - mapped_type_constant>::value...} { - } - - FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - - FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } - FMT_CONSTEXPR auto on_arg_id(int id) -> int { - return context_.check_arg_id(id), id; - } - FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { -#if FMT_USE_NONTYPE_TEMPLATE_ARGS - auto index = get_arg_index_by_name(id); - if (index == invalid_arg_index) on_error("named argument is not found"); - return context_.check_arg_id(index), index; -#else - (void)id; - on_error("compile-time checks for named arguments require C++20 support"); - return 0; -#endif - } - - FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} - - FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) - -> const Char* { - context_.advance_to(context_.begin() + (begin - &*context_.begin())); - // id >= 0 check is a workaround for gcc 10 bug (#2065). - return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; - } - - FMT_CONSTEXPR void on_error(const char* message) { - context_.on_error(message); - } -}; - -// Reports a compile-time error if S is not a valid format string. -template ::value)> -FMT_INLINE void check_format_string(const S&) { -#ifdef FMT_ENFORCE_COMPILE_STRING - static_assert(is_compile_string::value, - "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " - "FMT_STRING."); -#endif -} -template ::value)> -void check_format_string(S format_str) { - FMT_CONSTEXPR auto s = basic_string_view(format_str); - using checker = format_string_checker...>; - FMT_CONSTEXPR bool invalid_format = - (parse_format_string(s, checker(s, {})), true); - ignore_unused(invalid_format); -} - -template -void vformat_to( - buffer& buf, basic_string_view fmt, - basic_format_args)> args, - locale_ref loc = {}); - -FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); -#ifndef _WIN32 -inline void vprint_mojibake(std::FILE*, string_view, format_args) {} -#endif -FMT_END_DETAIL_NAMESPACE - -// A formatter specialization for the core types corresponding to detail::type -// constants. -template -struct formatter::value != - detail::type::custom_type>> { - private: - detail::dynamic_format_specs specs_; - - public: - // Parses format specifiers stopping either at the end of the range or at the - // terminating '}'. - template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin == end) return begin; - using handler_type = detail::dynamic_specs_handler; - auto type = detail::type_constant::value; - auto checker = - detail::specs_checker(handler_type(specs_, ctx), type); - auto it = detail::parse_format_specs(begin, end, checker); - auto eh = ctx.error_handler(); - switch (type) { - case detail::type::none_type: - FMT_ASSERT(false, "invalid argument type"); - break; - case detail::type::bool_type: - if (specs_.type == presentation_type::none || - specs_.type == presentation_type::string) { - break; - } - FMT_FALLTHROUGH; - case detail::type::int_type: - case detail::type::uint_type: - case detail::type::long_long_type: - case detail::type::ulong_long_type: - case detail::type::int128_type: - case detail::type::uint128_type: - detail::check_int_type_spec(specs_.type, eh); - break; - case detail::type::char_type: - detail::check_char_specs(specs_, eh); - break; - case detail::type::float_type: - if (detail::const_check(FMT_USE_FLOAT)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "float support disabled"); - break; - case detail::type::double_type: - if (detail::const_check(FMT_USE_DOUBLE)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "double support disabled"); - break; - case detail::type::long_double_type: - if (detail::const_check(FMT_USE_LONG_DOUBLE)) - detail::parse_float_type_spec(specs_, eh); - else - FMT_ASSERT(false, "long double support disabled"); - break; - case detail::type::cstring_type: - detail::check_cstring_type_spec(specs_.type, eh); - break; - case detail::type::string_type: - detail::check_string_type_spec(specs_.type, eh); - break; - case detail::type::pointer_type: - detail::check_pointer_type_spec(specs_.type, eh); - break; - case detail::type::custom_type: - // Custom format specifiers are checked in parse functions of - // formatter specializations. - break; - } - return it; - } - - template ::value, - enable_if_t<(U == detail::type::string_type || - U == detail::type::cstring_type || - U == detail::type::char_type), - int> = 0> - FMT_CONSTEXPR void set_debug_format() { - specs_.type = presentation_type::debug; - } - - template - FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const - -> decltype(ctx.out()); -}; - -#define FMT_FORMAT_AS(Type, Base) \ - template \ - struct formatter : formatter { \ - template \ - auto format(Type const& val, FormatContext& ctx) const \ - -> decltype(ctx.out()) { \ - return formatter::format(static_cast(val), ctx); \ - } \ - } - -FMT_FORMAT_AS(signed char, int); -FMT_FORMAT_AS(unsigned char, unsigned); -FMT_FORMAT_AS(short, int); -FMT_FORMAT_AS(unsigned short, unsigned); -FMT_FORMAT_AS(long, long long); -FMT_FORMAT_AS(unsigned long, unsigned long long); -FMT_FORMAT_AS(Char*, const Char*); -FMT_FORMAT_AS(std::basic_string, basic_string_view); -FMT_FORMAT_AS(std::nullptr_t, const void*); -FMT_FORMAT_AS(detail::std_string_view, basic_string_view); - -template struct basic_runtime { basic_string_view str; }; - -/** A compile-time format string. */ -template class basic_format_string { - private: - basic_string_view str_; - - public: - template >::value)> - FMT_CONSTEVAL FMT_INLINE basic_format_string(const S& s) : str_(s) { - static_assert( - detail::count< - (std::is_base_of>::value && - std::is_reference::value)...>() == 0, - "passing views as lvalues is disallowed"); -#ifdef FMT_HAS_CONSTEVAL - if constexpr (detail::count_named_args() == - detail::count_statically_named_args()) { - using checker = detail::format_string_checker...>; - detail::parse_format_string(str_, checker(s, {})); - } -#else - detail::check_format_string(s); -#endif - } - basic_format_string(basic_runtime r) : str_(r.str) {} - - FMT_INLINE operator basic_string_view() const { return str_; } -}; - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 -// Workaround broken conversion on older gcc. -template using format_string = string_view; -inline auto runtime(string_view s) -> string_view { return s; } -#else -template -using format_string = basic_format_string...>; -/** - \rst - Creates a runtime format string. - - **Example**:: - - // Check format string at runtime instead of compile-time. - fmt::print(fmt::runtime("{:d}"), "I am not a number"); - \endrst - */ -inline auto runtime(string_view s) -> basic_runtime { return {{s}}; } -#endif - -FMT_API auto vformat(string_view fmt, format_args args) -> std::string; - -/** - \rst - Formats ``args`` according to specifications in ``fmt`` and returns the result - as a string. - - **Example**:: - - #include - std::string message = fmt::format("The answer is {}.", 42); - \endrst -*/ -template -FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) - -> std::string { - return vformat(fmt, fmt::make_format_args(args...)); -} - -/** Formats a string and writes the output to ``out``. */ -template ::value)> -auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { - using detail::get_buffer; - auto&& buf = get_buffer(out); - detail::vformat_to(buf, fmt, args, {}); - return detail::get_iterator(buf); -} - -/** - \rst - Formats ``args`` according to specifications in ``fmt``, writes the result to - the output iterator ``out`` and returns the iterator past the end of the output - range. `format_to` does not append a terminating null character. - - **Example**:: - - auto out = std::vector(); - fmt::format_to(std::back_inserter(out), "{}", 42); - \endrst - */ -template ::value)> -FMT_INLINE auto format_to(OutputIt out, format_string fmt, T&&... args) - -> OutputIt { - return vformat_to(out, fmt, fmt::make_format_args(args...)); -} - -template struct format_to_n_result { - /** Iterator past the end of the output range. */ - OutputIt out; - /** Total (not truncated) output size. */ - size_t size; -}; - -template ::value)> -auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) - -> format_to_n_result { - using traits = detail::fixed_buffer_traits; - auto buf = detail::iterator_buffer(out, n); - detail::vformat_to(buf, fmt, args, {}); - return {buf.out(), buf.count()}; -} - -/** - \rst - Formats ``args`` according to specifications in ``fmt``, writes up to ``n`` - characters of the result to the output iterator ``out`` and returns the total - (not truncated) output size and the iterator past the end of the output range. - `format_to_n` does not append a terminating null character. - \endrst - */ -template ::value)> -FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, - T&&... args) -> format_to_n_result { - return vformat_to_n(out, n, fmt, fmt::make_format_args(args...)); -} - -/** Returns the number of chars in the output of ``format(fmt, args...)``. */ -template -FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, - T&&... args) -> size_t { - auto buf = detail::counting_buffer<>(); - detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...), {}); - return buf.count(); -} - -FMT_API void vprint(string_view fmt, format_args args); -FMT_API void vprint(std::FILE* f, string_view fmt, format_args args); - -/** - \rst - Formats ``args`` according to specifications in ``fmt`` and writes the output - to ``stdout``. - - **Example**:: - - fmt::print("Elapsed time: {0:.2f} seconds", 1.23); - \endrst - */ -template -FMT_INLINE void print(format_string fmt, T&&... args) { - const auto& vargs = fmt::make_format_args(args...); - return detail::is_utf8() ? vprint(fmt, vargs) - : detail::vprint_mojibake(stdout, fmt, vargs); -} - -/** - \rst - Formats ``args`` according to specifications in ``fmt`` and writes the - output to the file ``f``. - - **Example**:: - - fmt::print(stderr, "Don't {}!", "panic"); - \endrst - */ -template -FMT_INLINE void print(std::FILE* f, format_string fmt, T&&... args) { - const auto& vargs = fmt::make_format_args(args...); - return detail::is_utf8() ? vprint(f, fmt, vargs) - : detail::vprint_mojibake(f, fmt, vargs); -} - -FMT_MODULE_EXPORT_END -FMT_GCC_PRAGMA("GCC pop_options") -FMT_END_NAMESPACE - -#ifdef FMT_HEADER_ONLY -# include "format.h" -#endif -#endif // FMT_CORE_H_ diff --git a/Externals/fmt/include/fmt/format-inl.h b/Externals/fmt/include/fmt/format-inl.h deleted file mode 100755 index 22b1ec8df0..0000000000 --- a/Externals/fmt/include/fmt/format-inl.h +++ /dev/null @@ -1,1723 +0,0 @@ -// Formatting library for C++ - implementation -// -// Copyright (c) 2012 - 2016, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_FORMAT_INL_H_ -#define FMT_FORMAT_INL_H_ - -#include -#include -#include // errno -#include -#include -#include -#include // std::memmove -#include -#include - -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR -# include -#endif - -#ifdef _WIN32 -# include // _isatty -#endif - -#include "format.h" - -FMT_BEGIN_NAMESPACE -namespace detail { - -FMT_FUNC void assert_fail(const char* file, int line, const char* message) { - // Use unchecked std::fprintf to avoid triggering another assertion when - // writing to stderr fails - std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); - // Chosen instead of std::abort to satisfy Clang in CUDA mode during device - // code pass. - std::terminate(); -} - -FMT_FUNC void throw_format_error(const char* message) { - FMT_THROW(format_error(message)); -} - -FMT_FUNC void format_error_code(detail::buffer& out, int error_code, - string_view message) noexcept { - // Report error code making sure that the output fits into - // inline_buffer_size to avoid dynamic memory allocation and potential - // bad_alloc. - out.try_resize(0); - static const char SEP[] = ": "; - static const char ERROR_STR[] = "error "; - // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. - size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; - auto abs_value = static_cast>(error_code); - if (detail::is_negative(error_code)) { - abs_value = 0 - abs_value; - ++error_code_size; - } - error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); - auto it = buffer_appender(out); - if (message.size() <= inline_buffer_size - error_code_size) - format_to(it, FMT_STRING("{}{}"), message, SEP); - format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); - FMT_ASSERT(out.size() <= inline_buffer_size, ""); -} - -FMT_FUNC void report_error(format_func func, int error_code, - const char* message) noexcept { - memory_buffer full_message; - func(full_message, error_code, message); - // Don't use fwrite_fully because the latter may throw. - if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0) - std::fputc('\n', stderr); -} - -// A wrapper around fwrite that throws on error. -inline void fwrite_fully(const void* ptr, size_t size, size_t count, - FILE* stream) { - size_t written = std::fwrite(ptr, size, count, stream); - if (written < count) - FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); -} - -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR -template -locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { - static_assert(std::is_same::value, ""); -} - -template Locale locale_ref::get() const { - static_assert(std::is_same::value, ""); - return locale_ ? *static_cast(locale_) : std::locale(); -} - -template -FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { - auto& facet = std::use_facet>(loc.get()); - auto grouping = facet.grouping(); - auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); - return {std::move(grouping), thousands_sep}; -} -template FMT_FUNC Char decimal_point_impl(locale_ref loc) { - return std::use_facet>(loc.get()) - .decimal_point(); -} -#else -template -FMT_FUNC auto thousands_sep_impl(locale_ref) -> thousands_sep_result { - return {"\03", FMT_STATIC_THOUSANDS_SEPARATOR}; -} -template FMT_FUNC Char decimal_point_impl(locale_ref) { - return '.'; -} -#endif -} // namespace detail - -#if !FMT_MSC_VERSION -FMT_API FMT_FUNC format_error::~format_error() noexcept = default; -#endif - -FMT_FUNC std::system_error vsystem_error(int error_code, string_view format_str, - format_args args) { - auto ec = std::error_code(error_code, std::generic_category()); - return std::system_error(ec, vformat(format_str, args)); -} - -namespace detail { - -template inline bool operator==(basic_fp x, basic_fp y) { - return x.f == y.f && x.e == y.e; -} - -// Compilers should be able to optimize this into the ror instruction. -FMT_CONSTEXPR inline uint32_t rotr(uint32_t n, uint32_t r) noexcept { - r &= 31; - return (n >> r) | (n << (32 - r)); -} -FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept { - r &= 63; - return (n >> r) | (n << (64 - r)); -} - -// Computes 128-bit result of multiplication of two 64-bit unsigned integers. -inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { -#if FMT_USE_INT128 - auto p = static_cast(x) * static_cast(y); - return {static_cast(p >> 64), static_cast(p)}; -#elif defined(_MSC_VER) && defined(_M_X64) - auto result = uint128_fallback(); - result.lo_ = _umul128(x, y, &result.hi_); - return result; -#else - const uint64_t mask = static_cast(max_value()); - - uint64_t a = x >> 32; - uint64_t b = x & mask; - uint64_t c = y >> 32; - uint64_t d = y & mask; - - uint64_t ac = a * c; - uint64_t bc = b * c; - uint64_t ad = a * d; - uint64_t bd = b * d; - - uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask); - - return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32), - (intermediate << 32) + (bd & mask)}; -#endif -} - -// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox. -namespace dragonbox { -// Computes upper 64 bits of multiplication of two 64-bit unsigned integers. -inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { -#if FMT_USE_INT128 - auto p = static_cast(x) * static_cast(y); - return static_cast(p >> 64); -#elif defined(_MSC_VER) && defined(_M_X64) - return __umulh(x, y); -#else - return umul128(x, y).high(); -#endif -} - -// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a -// 128-bit unsigned integer. -inline uint128_fallback umul192_upper128(uint64_t x, - uint128_fallback y) noexcept { - uint128_fallback r = umul128(x, y.high()); - r += umul128_upper64(x, y.low()); - return r; -} - -// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a -// 64-bit unsigned integer. -inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept { - return umul128_upper64(static_cast(x) << 32, y); -} - -// Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a -// 128-bit unsigned integer. -inline uint128_fallback umul192_lower128(uint64_t x, - uint128_fallback y) noexcept { - uint64_t high = x * y.high(); - uint128_fallback high_low = umul128(x, y.low()); - return {high + high_low.high(), high_low.low()}; -} - -// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a -// 64-bit unsigned integer. -inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept { - return x * y; -} - -// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from -// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. -inline int floor_log10_pow2(int e) noexcept { - FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); - static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); - return (e * 315653) >> 20; -} - -// Various fast log computations. -inline int floor_log2_pow10(int e) noexcept { - FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); - return (e * 1741647) >> 19; -} -inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept { - FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); - return (e * 631305 - 261663) >> 21; -} - -static constexpr struct { - uint32_t divisor; - int shift_amount; -} div_small_pow10_infos[] = {{10, 16}, {100, 16}}; - -// Replaces n by floor(n / pow(10, N)) returning true if and only if n is -// divisible by pow(10, N). -// Precondition: n <= pow(10, N + 1). -template -bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept { - // The numbers below are chosen such that: - // 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100, - // 2. nm mod 2^k < m if and only if n is divisible by d, - // where m is magic_number, k is shift_amount - // and d is divisor. - // - // Item 1 is a common technique of replacing division by a constant with - // multiplication, see e.g. "Division by Invariant Integers Using - // Multiplication" by Granlund and Montgomery (1994). magic_number (m) is set - // to ceil(2^k/d) for large enough k. - // The idea for item 2 originates from Schubfach. - constexpr auto info = div_small_pow10_infos[N - 1]; - FMT_ASSERT(n <= info.divisor * 10, "n is too large"); - constexpr uint32_t magic_number = - (1u << info.shift_amount) / info.divisor + 1; - n *= magic_number; - const uint32_t comparison_mask = (1u << info.shift_amount) - 1; - bool result = (n & comparison_mask) < magic_number; - n >>= info.shift_amount; - return result; -} - -// Computes floor(n / pow(10, N)) for small n and N. -// Precondition: n <= pow(10, N + 1). -template uint32_t small_division_by_pow10(uint32_t n) noexcept { - constexpr auto info = div_small_pow10_infos[N - 1]; - FMT_ASSERT(n <= info.divisor * 10, "n is too large"); - constexpr uint32_t magic_number = - (1u << info.shift_amount) / info.divisor + 1; - return (n * magic_number) >> info.shift_amount; -} - -// Computes floor(n / 10^(kappa + 1)) (float) -inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) noexcept { - // 1374389535 = ceil(2^37/100) - return static_cast((static_cast(n) * 1374389535) >> 37); -} -// Computes floor(n / 10^(kappa + 1)) (double) -inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept { - // 2361183241434822607 = ceil(2^(64+7)/1000) - return umul128_upper64(n, 2361183241434822607ull) >> 7; -} - -// Various subroutines using pow10 cache -template struct cache_accessor; - -template <> struct cache_accessor { - using carrier_uint = float_info::carrier_uint; - using cache_entry_type = uint64_t; - - static uint64_t get_cached_power(int k) noexcept { - FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, - "k is out of range"); - static constexpr const uint64_t pow10_significands[] = { - 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, - 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, - 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, - 0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb, - 0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a, - 0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810, - 0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff, - 0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd, - 0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424, - 0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b, - 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000, - 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, - 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, - 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, - 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, - 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, - 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, - 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, - 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, - 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940985, - 0xa18f07d736b90be6, 0xc9f2c9cd04674edf, 0xfc6f7c4045812297, - 0x9dc5ada82b70b59e, 0xc5371912364ce306, 0xf684df56c3e01bc7, - 0x9a130b963a6c115d, 0xc097ce7bc90715b4, 0xf0bdc21abb48db21, - 0x96769950b50d88f5, 0xbc143fa4e250eb32, 0xeb194f8e1ae525fe, - 0x92efd1b8d0cf37bf, 0xb7abc627050305ae, 0xe596b7b0c643c71a, - 0x8f7e32ce7bea5c70, 0xb35dbf821ae4f38c, 0xe0352f62a19e306f}; - return pow10_significands[k - float_info::min_k]; - } - - struct compute_mul_result { - carrier_uint result; - bool is_integer; - }; - struct compute_mul_parity_result { - bool parity; - bool is_integer; - }; - - static compute_mul_result compute_mul( - carrier_uint u, const cache_entry_type& cache) noexcept { - auto r = umul96_upper64(u, cache); - return {static_cast(r >> 32), - static_cast(r) == 0}; - } - - static uint32_t compute_delta(const cache_entry_type& cache, - int beta) noexcept { - return static_cast(cache >> (64 - 1 - beta)); - } - - static compute_mul_parity_result compute_mul_parity( - carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept { - FMT_ASSERT(beta >= 1, ""); - FMT_ASSERT(beta < 64, ""); - - auto r = umul96_lower64(two_f, cache); - return {((r >> (64 - beta)) & 1) != 0, - static_cast(r >> (32 - beta)) == 0}; - } - - static carrier_uint compute_left_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { - return static_cast( - (cache - (cache >> (num_significand_bits() + 2))) >> - (64 - num_significand_bits() - 1 - beta)); - } - - static carrier_uint compute_right_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { - return static_cast( - (cache + (cache >> (num_significand_bits() + 1))) >> - (64 - num_significand_bits() - 1 - beta)); - } - - static carrier_uint compute_round_up_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { - return (static_cast( - cache >> (64 - num_significand_bits() - 2 - beta)) + - 1) / - 2; - } -}; - -template <> struct cache_accessor { - using carrier_uint = float_info::carrier_uint; - using cache_entry_type = uint128_fallback; - - static uint128_fallback get_cached_power(int k) noexcept { - FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, - "k is out of range"); - - static constexpr const uint128_fallback pow10_significands[] = { -#if FMT_USE_FULL_CACHE_DRAGONBOX - {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, - {0x9faacf3df73609b1, 0x77b191618c54e9ad}, - {0xc795830d75038c1d, 0xd59df5b9ef6a2418}, - {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e}, - {0x9becce62836ac577, 0x4ee367f9430aec33}, - {0xc2e801fb244576d5, 0x229c41f793cda740}, - {0xf3a20279ed56d48a, 0x6b43527578c11110}, - {0x9845418c345644d6, 0x830a13896b78aaaa}, - {0xbe5691ef416bd60c, 0x23cc986bc656d554}, - {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9}, - {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa}, - {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54}, - {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69}, - {0x91376c36d99995be, 0x23100809b9c21fa2}, - {0xb58547448ffffb2d, 0xabd40a0c2832a78b}, - {0xe2e69915b3fff9f9, 0x16c90c8f323f516d}, - {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4}, - {0xb1442798f49ffb4a, 0x99cd11cfdf41779d}, - {0xdd95317f31c7fa1d, 0x40405643d711d584}, - {0x8a7d3eef7f1cfc52, 0x482835ea666b2573}, - {0xad1c8eab5ee43b66, 0xda3243650005eed0}, - {0xd863b256369d4a40, 0x90bed43e40076a83}, - {0x873e4f75e2224e68, 0x5a7744a6e804a292}, - {0xa90de3535aaae202, 0x711515d0a205cb37}, - {0xd3515c2831559a83, 0x0d5a5b44ca873e04}, - {0x8412d9991ed58091, 0xe858790afe9486c3}, - {0xa5178fff668ae0b6, 0x626e974dbe39a873}, - {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, - {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a}, - {0xa139029f6a239f72, 0x1c1fffc1ebc44e81}, - {0xc987434744ac874e, 0xa327ffb266b56221}, - {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9}, - {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa}, - {0xc4ce17b399107c22, 0xcb550fb4384d21d4}, - {0xf6019da07f549b2b, 0x7e2a53a146606a49}, - {0x99c102844f94e0fb, 0x2eda7444cbfc426e}, - {0xc0314325637a1939, 0xfa911155fefb5309}, - {0xf03d93eebc589f88, 0x793555ab7eba27cb}, - {0x96267c7535b763b5, 0x4bc1558b2f3458df}, - {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17}, - {0xea9c227723ee8bcb, 0x465e15a979c1cadd}, - {0x92a1958a7675175f, 0x0bfacd89ec191eca}, - {0xb749faed14125d36, 0xcef980ec671f667c}, - {0xe51c79a85916f484, 0x82b7e12780e7401b}, - {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811}, - {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16}, - {0xdfbdcece67006ac9, 0x67a791e093e1d49b}, - {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1}, - {0xaecc49914078536d, 0x58fae9f773886e19}, - {0xda7f5bf590966848, 0xaf39a475506a899f}, - {0x888f99797a5e012d, 0x6d8406c952429604}, - {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84}, - {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65}, - {0x855c3be0a17fcd26, 0x5cf2eea09a550680}, - {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, - {0xd0601d8efc57b08b, 0xf13b94daf124da27}, - {0x823c12795db6ce57, 0x76c53d08d6b70859}, - {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f}, - {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a}, - {0xfe5d54150b090b02, 0xd3f93b35435d7c4d}, - {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0}, - {0xc6b8e9b0709f109a, 0x359ab6419ca1091c}, - {0xf867241c8cc6d4c0, 0xc30163d203c94b63}, - {0x9b407691d7fc44f8, 0x79e0de63425dcf1e}, - {0xc21094364dfb5636, 0x985915fc12f542e5}, - {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e}, - {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43}, - {0xbd8430bd08277231, 0x50c6ff782a838354}, - {0xece53cec4a314ebd, 0xa4f8bf5635246429}, - {0x940f4613ae5ed136, 0x871b7795e136be9a}, - {0xb913179899f68584, 0x28e2557b59846e40}, - {0xe757dd7ec07426e5, 0x331aeada2fe589d0}, - {0x9096ea6f3848984f, 0x3ff0d2c85def7622}, - {0xb4bca50b065abe63, 0x0fed077a756b53aa}, - {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895}, - {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d}, - {0xb080392cc4349dec, 0xbd8d794d96aacfb4}, - {0xdca04777f541c567, 0xecf0d7a0fc5583a1}, - {0x89e42caaf9491b60, 0xf41686c49db57245}, - {0xac5d37d5b79b6239, 0x311c2875c522ced6}, - {0xd77485cb25823ac7, 0x7d633293366b828c}, - {0x86a8d39ef77164bc, 0xae5dff9c02033198}, - {0xa8530886b54dbdeb, 0xd9f57f830283fdfd}, - {0xd267caa862a12d66, 0xd072df63c324fd7c}, - {0x8380dea93da4bc60, 0x4247cb9e59f71e6e}, - {0xa46116538d0deb78, 0x52d9be85f074e609}, - {0xcd795be870516656, 0x67902e276c921f8c}, - {0x806bd9714632dff6, 0x00ba1cd8a3db53b7}, - {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5}, - {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce}, - {0xfad2a4b13d1b5d6c, 0x796b805720085f82}, - {0x9cc3a6eec6311a63, 0xcbe3303674053bb1}, - {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d}, - {0xf4f1b4d515acb93b, 0xee92fb5515482d45}, - {0x991711052d8bf3c5, 0x751bdd152d4d1c4b}, - {0xbf5cd54678eef0b6, 0xd262d45a78a0635e}, - {0xef340a98172aace4, 0x86fb897116c87c35}, - {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1}, - {0xbae0a846d2195712, 0x8974836059cca10a}, - {0xe998d258869facd7, 0x2bd1a438703fc94c}, - {0x91ff83775423cc06, 0x7b6306a34627ddd0}, - {0xb67f6455292cbf08, 0x1a3bc84c17b1d543}, - {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94}, - {0x8e938662882af53e, 0x547eb47b7282ee9d}, - {0xb23867fb2a35b28d, 0xe99e619a4f23aa44}, - {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5}, - {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05}, - {0xae0b158b4738705e, 0x9624ab50b148d446}, - {0xd98ddaee19068c76, 0x3badd624dd9b0958}, - {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7}, - {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d}, - {0xd47487cc8470652b, 0x7647c32000696720}, - {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074}, - {0xa5fb0a17c777cf09, 0xf468107100525891}, - {0xcf79cc9db955c2cc, 0x7182148d4066eeb5}, - {0x81ac1fe293d599bf, 0xc6f14cd848405531}, - {0xa21727db38cb002f, 0xb8ada00e5a506a7d}, - {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d}, - {0xfd442e4688bd304a, 0x908f4a166d1da664}, - {0x9e4a9cec15763e2e, 0x9a598e4e043287ff}, - {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe}, - {0xf7549530e188c128, 0xd12bee59e68ef47d}, - {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf}, - {0xc13a148e3032d6e7, 0xe36a52363c1faf02}, - {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2}, - {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba}, - {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8}, - {0xebdf661791d60f56, 0x111b495b3464ad22}, - {0x936b9fcebb25c995, 0xcab10dd900beec35}, - {0xb84687c269ef3bfb, 0x3d5d514f40eea743}, - {0xe65829b3046b0afa, 0x0cb4a5a3112a5113}, - {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac}, - {0xb3f4e093db73a093, 0x59ed216765690f57}, - {0xe0f218b8d25088b8, 0x306869c13ec3532d}, - {0x8c974f7383725573, 0x1e414218c73a13fc}, - {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, - {0xdbac6c247d62a583, 0xdf45f746b74abf3a}, - {0x894bc396ce5da772, 0x6b8bba8c328eb784}, - {0xab9eb47c81f5114f, 0x066ea92f3f326565}, - {0xd686619ba27255a2, 0xc80a537b0efefebe}, - {0x8613fd0145877585, 0xbd06742ce95f5f37}, - {0xa798fc4196e952e7, 0x2c48113823b73705}, - {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6}, - {0x82ef85133de648c4, 0x9a984d73dbe722fc}, - {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb}, - {0xcc963fee10b7d1b3, 0x318df905079926a9}, - {0xffbbcfe994e5c61f, 0xfdf17746497f7053}, - {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634}, - {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1}, - {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1}, - {0x9c1661a651213e2d, 0x06bea10ca65c084f}, - {0xc31bfa0fe5698db8, 0x486e494fcff30a63}, - {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb}, - {0x986ddb5c6b3a76b7, 0xf89629465a75e01d}, - {0xbe89523386091465, 0xf6bbb397f1135824}, - {0xee2ba6c0678b597f, 0x746aa07ded582e2d}, - {0x94db483840b717ef, 0xa8c2a44eb4571cdd}, - {0xba121a4650e4ddeb, 0x92f34d62616ce414}, - {0xe896a0d7e51e1566, 0x77b020baf9c81d18}, - {0x915e2486ef32cd60, 0x0ace1474dc1d122f}, - {0xb5b5ada8aaff80b8, 0x0d819992132456bb}, - {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a}, - {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, - {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3}, - {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf}, - {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c}, - {0xad4ab7112eb3929d, 0x86c16c98d2c953c7}, - {0xd89d64d57a607744, 0xe871c7bf077ba8b8}, - {0x87625f056c7c4a8b, 0x11471cd764ad4973}, - {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0}, - {0xd389b47879823479, 0x4aff1d108d4ec2c4}, - {0x843610cb4bf160cb, 0xcedf722a585139bb}, - {0xa54394fe1eedb8fe, 0xc2974eb4ee658829}, - {0xce947a3da6a9273e, 0x733d226229feea33}, - {0x811ccc668829b887, 0x0806357d5a3f5260}, - {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8}, - {0xc9bcff6034c13052, 0xfc89b393dd02f0b6}, - {0xfc2c3f3841f17c67, 0xbbac2078d443ace3}, - {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e}, - {0xc5029163f384a931, 0x0a9e795e65d4df12}, - {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6}, - {0x99ea0196163fa42e, 0x504bced1bf8e4e46}, - {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7}, - {0xf07da27a82c37088, 0x5d767327bb4e5a4d}, - {0x964e858c91ba2655, 0x3a6a07f8d510f870}, - {0xbbe226efb628afea, 0x890489f70a55368c}, - {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f}, - {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e}, - {0xb77ada0617e3bbcb, 0x09ce6ebb40173745}, - {0xe55990879ddcaabd, 0xcc420a6a101d0516}, - {0x8f57fa54c2a9eab6, 0x9fa946824a12232e}, - {0xb32df8e9f3546564, 0x47939822dc96abfa}, - {0xdff9772470297ebd, 0x59787e2b93bc56f8}, - {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b}, - {0xaefae51477a06b03, 0xede622920b6b23f2}, - {0xdab99e59958885c4, 0xe95fab368e45ecee}, - {0x88b402f7fd75539b, 0x11dbcb0218ebb415}, - {0xaae103b5fcd2a881, 0xd652bdc29f26a11a}, - {0xd59944a37c0752a2, 0x4be76d3346f04960}, - {0x857fcae62d8493a5, 0x6f70a4400c562ddc}, - {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953}, - {0xd097ad07a71f26b2, 0x7e2000a41346a7a8}, - {0x825ecc24c873782f, 0x8ed400668c0c28c9}, - {0xa2f67f2dfa90563b, 0x728900802f0f32fb}, - {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba}, - {0xfea126b7d78186bc, 0xe2f610c84987bfa9}, - {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca}, - {0xc6ede63fa05d3143, 0x91503d1c79720dbc}, - {0xf8a95fcf88747d94, 0x75a44c6397ce912b}, - {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb}, - {0xc24452da229b021b, 0xfbe85badce996169}, - {0xf2d56790ab41c2a2, 0xfae27299423fb9c4}, - {0x97c560ba6b0919a5, 0xdccd879fc967d41b}, - {0xbdb6b8e905cb600f, 0x5400e987bbc1c921}, - {0xed246723473e3813, 0x290123e9aab23b69}, - {0x9436c0760c86e30b, 0xf9a0b6720aaf6522}, - {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, - {0xe7958cb87392c2c2, 0xb60b1d1230b20e05}, - {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3}, - {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4}, - {0xe2280b6c20dd5232, 0x25c6da63c38de1b1}, - {0x8d590723948a535f, 0x579c487e5a38ad0f}, - {0xb0af48ec79ace837, 0x2d835a9df0c6d852}, - {0xdcdb1b2798182244, 0xf8e431456cf88e66}, - {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900}, - {0xac8b2d36eed2dac5, 0xe272467e3d222f40}, - {0xd7adf884aa879177, 0x5b0ed81dcc6abb10}, - {0x86ccbb52ea94baea, 0x98e947129fc2b4ea}, - {0xa87fea27a539e9a5, 0x3f2398d747b36225}, - {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae}, - {0x83a3eeeef9153e89, 0x1953cf68300424ad}, - {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8}, - {0xcdb02555653131b6, 0x3792f412cb06794e}, - {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1}, - {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5}, - {0xc8de047564d20a8b, 0xf245825a5a445276}, - {0xfb158592be068d2e, 0xeed6e2f0f0d56713}, - {0x9ced737bb6c4183d, 0x55464dd69685606c}, - {0xc428d05aa4751e4c, 0xaa97e14c3c26b887}, - {0xf53304714d9265df, 0xd53dd99f4b3066a9}, - {0x993fe2c6d07b7fab, 0xe546a8038efe402a}, - {0xbf8fdb78849a5f96, 0xde98520472bdd034}, - {0xef73d256a5c0f77c, 0x963e66858f6d4441}, - {0x95a8637627989aad, 0xdde7001379a44aa9}, - {0xbb127c53b17ec159, 0x5560c018580d5d53}, - {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7}, - {0x9226712162ab070d, 0xcab3961304ca70e9}, - {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23}, - {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b}, - {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243}, - {0xb267ed1940f1c61c, 0x55f038b237591ed4}, - {0xdf01e85f912e37a3, 0x6b6c46dec52f6689}, - {0x8b61313bbabce2c6, 0x2323ac4b3b3da016}, - {0xae397d8aa96c1b77, 0xabec975e0a0d081b}, - {0xd9c7dced53c72255, 0x96e7bd358c904a22}, - {0x881cea14545c7575, 0x7e50d64177da2e55}, - {0xaa242499697392d2, 0xdde50bd1d5d0b9ea}, - {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865}, - {0x84ec3c97da624ab4, 0xbd5af13bef0b113f}, - {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f}, - {0xcfb11ead453994ba, 0x67de18eda5814af3}, - {0x81ceb32c4b43fcf4, 0x80eacf948770ced8}, - {0xa2425ff75e14fc31, 0xa1258379a94d028e}, - {0xcad2f7f5359a3b3e, 0x096ee45813a04331}, - {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd}, - {0x9e74d1b791e07e48, 0x775ea264cf55347e}, - {0xc612062576589dda, 0x95364afe032a819e}, - {0xf79687aed3eec551, 0x3a83ddbd83f52205}, - {0x9abe14cd44753b52, 0xc4926a9672793543}, - {0xc16d9a0095928a27, 0x75b7053c0f178294}, - {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, - {0x971da05074da7bee, 0xd3f6fc16ebca5e04}, - {0xbce5086492111aea, 0x88f4bb1ca6bcf585}, - {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6}, - {0x9392ee8e921d5d07, 0x3aff322e62439fd0}, - {0xb877aa3236a4b449, 0x09befeb9fad487c3}, - {0xe69594bec44de15b, 0x4c2ebe687989a9b4}, - {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11}, - {0xb424dc35095cd80f, 0x538484c19ef38c95}, - {0xe12e13424bb40e13, 0x2865a5f206b06fba}, - {0x8cbccc096f5088cb, 0xf93f87b7442e45d4}, - {0xafebff0bcb24aafe, 0xf78f69a51539d749}, - {0xdbe6fecebdedd5be, 0xb573440e5a884d1c}, - {0x89705f4136b4a597, 0x31680a88f8953031}, - {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e}, - {0xd6bf94d5e57a42bc, 0x3d32907604691b4d}, - {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110}, - {0xa7c5ac471b478423, 0x0fcf80dc33721d54}, - {0xd1b71758e219652b, 0xd3c36113404ea4a9}, - {0x83126e978d4fdf3b, 0x645a1cac083126ea}, - {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4}, - {0xcccccccccccccccc, 0xcccccccccccccccd}, - {0x8000000000000000, 0x0000000000000000}, - {0xa000000000000000, 0x0000000000000000}, - {0xc800000000000000, 0x0000000000000000}, - {0xfa00000000000000, 0x0000000000000000}, - {0x9c40000000000000, 0x0000000000000000}, - {0xc350000000000000, 0x0000000000000000}, - {0xf424000000000000, 0x0000000000000000}, - {0x9896800000000000, 0x0000000000000000}, - {0xbebc200000000000, 0x0000000000000000}, - {0xee6b280000000000, 0x0000000000000000}, - {0x9502f90000000000, 0x0000000000000000}, - {0xba43b74000000000, 0x0000000000000000}, - {0xe8d4a51000000000, 0x0000000000000000}, - {0x9184e72a00000000, 0x0000000000000000}, - {0xb5e620f480000000, 0x0000000000000000}, - {0xe35fa931a0000000, 0x0000000000000000}, - {0x8e1bc9bf04000000, 0x0000000000000000}, - {0xb1a2bc2ec5000000, 0x0000000000000000}, - {0xde0b6b3a76400000, 0x0000000000000000}, - {0x8ac7230489e80000, 0x0000000000000000}, - {0xad78ebc5ac620000, 0x0000000000000000}, - {0xd8d726b7177a8000, 0x0000000000000000}, - {0x878678326eac9000, 0x0000000000000000}, - {0xa968163f0a57b400, 0x0000000000000000}, - {0xd3c21bcecceda100, 0x0000000000000000}, - {0x84595161401484a0, 0x0000000000000000}, - {0xa56fa5b99019a5c8, 0x0000000000000000}, - {0xcecb8f27f4200f3a, 0x0000000000000000}, - {0x813f3978f8940984, 0x4000000000000000}, - {0xa18f07d736b90be5, 0x5000000000000000}, - {0xc9f2c9cd04674ede, 0xa400000000000000}, - {0xfc6f7c4045812296, 0x4d00000000000000}, - {0x9dc5ada82b70b59d, 0xf020000000000000}, - {0xc5371912364ce305, 0x6c28000000000000}, - {0xf684df56c3e01bc6, 0xc732000000000000}, - {0x9a130b963a6c115c, 0x3c7f400000000000}, - {0xc097ce7bc90715b3, 0x4b9f100000000000}, - {0xf0bdc21abb48db20, 0x1e86d40000000000}, - {0x96769950b50d88f4, 0x1314448000000000}, - {0xbc143fa4e250eb31, 0x17d955a000000000}, - {0xeb194f8e1ae525fd, 0x5dcfab0800000000}, - {0x92efd1b8d0cf37be, 0x5aa1cae500000000}, - {0xb7abc627050305ad, 0xf14a3d9e40000000}, - {0xe596b7b0c643c719, 0x6d9ccd05d0000000}, - {0x8f7e32ce7bea5c6f, 0xe4820023a2000000}, - {0xb35dbf821ae4f38b, 0xdda2802c8a800000}, - {0xe0352f62a19e306e, 0xd50b2037ad200000}, - {0x8c213d9da502de45, 0x4526f422cc340000}, - {0xaf298d050e4395d6, 0x9670b12b7f410000}, - {0xdaf3f04651d47b4c, 0x3c0cdd765f114000}, - {0x88d8762bf324cd0f, 0xa5880a69fb6ac800}, - {0xab0e93b6efee0053, 0x8eea0d047a457a00}, - {0xd5d238a4abe98068, 0x72a4904598d6d880}, - {0x85a36366eb71f041, 0x47a6da2b7f864750}, - {0xa70c3c40a64e6c51, 0x999090b65f67d924}, - {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d}, - {0x82818f1281ed449f, 0xbff8f10e7a8921a5}, - {0xa321f2d7226895c7, 0xaff72d52192b6a0e}, - {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764491}, - {0xfee50b7025c36a08, 0x02f236d04753d5b5}, - {0x9f4f2726179a2245, 0x01d762422c946591}, - {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef6}, - {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb3}, - {0x9b934c3b330c8577, 0x63cc55f49f88eb30}, - {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fc}, - {0xf316271c7fc3908a, 0x8bef464e3945ef7b}, - {0x97edd871cfda3a56, 0x97758bf0e3cbb5ad}, - {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea318}, - {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bde}, - {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6b}, - {0xb975d6b6ee39e436, 0xb3e2fd538e122b45}, - {0xe7d34c64a9c85d44, 0x60dbbca87196b617}, - {0x90e40fbeea1d3a4a, 0xbc8955e946fe31ce}, - {0xb51d13aea4a488dd, 0x6babab6398bdbe42}, - {0xe264589a4dcdab14, 0xc696963c7eed2dd2}, - {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca3}, - {0xb0de65388cc8ada8, 0x3b25a55f43294bcc}, - {0xdd15fe86affad912, 0x49ef0eb713f39ebf}, - {0x8a2dbf142dfcc7ab, 0x6e3569326c784338}, - {0xacb92ed9397bf996, 0x49c2c37f07965405}, - {0xd7e77a8f87daf7fb, 0xdc33745ec97be907}, - {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a4}, - {0xa8acd7c0222311bc, 0xc40832ea0d68ce0d}, - {0xd2d80db02aabd62b, 0xf50a3fa490c30191}, - {0x83c7088e1aab65db, 0x792667c6da79e0fb}, - {0xa4b8cab1a1563f52, 0x577001b891185939}, - {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, - {0x80b05e5ac60b6178, 0x544f8158315b05b5}, - {0xa0dc75f1778e39d6, 0x696361ae3db1c722}, - {0xc913936dd571c84c, 0x03bc3a19cd1e38ea}, - {0xfb5878494ace3a5f, 0x04ab48a04065c724}, - {0x9d174b2dcec0e47b, 0x62eb0d64283f9c77}, - {0xc45d1df942711d9a, 0x3ba5d0bd324f8395}, - {0xf5746577930d6500, 0xca8f44ec7ee3647a}, - {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecc}, - {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67f}, - {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101f}, - {0x95d04aee3b80ece5, 0xbba1f1d158724a13}, - {0xbb445da9ca61281f, 0x2a8a6e45ae8edc98}, - {0xea1575143cf97226, 0xf52d09d71a3293be}, - {0x924d692ca61be758, 0x593c2626705f9c57}, - {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836d}, - {0xe498f455c38b997a, 0x0b6dfb9c0f956448}, - {0x8edf98b59a373fec, 0x4724bd4189bd5ead}, - {0xb2977ee300c50fe7, 0x58edec91ec2cb658}, - {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ee}, - {0x8b865b215899f46c, 0xbd79e0d20082ee75}, - {0xae67f1e9aec07187, 0xecd8590680a3aa12}, - {0xda01ee641a708de9, 0xe80e6f4820cc9496}, - {0x884134fe908658b2, 0x3109058d147fdcde}, - {0xaa51823e34a7eede, 0xbd4b46f0599fd416}, - {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91b}, - {0x850fadc09923329e, 0x03e2cf6bc604ddb1}, - {0xa6539930bf6bff45, 0x84db8346b786151d}, - {0xcfe87f7cef46ff16, 0xe612641865679a64}, - {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07f}, - {0xa26da3999aef7749, 0xe3be5e330f38f09e}, - {0xcb090c8001ab551c, 0x5cadf5bfd3072cc6}, - {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f7}, - {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afb}, - {0xc646d63501a1511d, 0xb281e1fd541501b9}, - {0xf7d88bc24209a565, 0x1f225a7ca91a4227}, - {0x9ae757596946075f, 0x3375788de9b06959}, - {0xc1a12d2fc3978937, 0x0052d6b1641c83af}, - {0xf209787bb47d6b84, 0xc0678c5dbd23a49b}, - {0x9745eb4d50ce6332, 0xf840b7ba963646e1}, - {0xbd176620a501fbff, 0xb650e5a93bc3d899}, - {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebf}, - {0x93ba47c980e98cdf, 0xc66f336c36b10138}, - {0xb8a8d9bbe123f017, 0xb80b0047445d4185}, - {0xe6d3102ad96cec1d, 0xa60dc059157491e6}, - {0x9043ea1ac7e41392, 0x87c89837ad68db30}, - {0xb454e4a179dd1877, 0x29babe4598c311fc}, - {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67b}, - {0x8ce2529e2734bb1d, 0x1899e4a65f58660d}, - {0xb01ae745b101e9e4, 0x5ec05dcff72e7f90}, - {0xdc21a1171d42645d, 0x76707543f4fa1f74}, - {0x899504ae72497eba, 0x6a06494a791c53a9}, - {0xabfa45da0edbde69, 0x0487db9d17636893}, - {0xd6f8d7509292d603, 0x45a9d2845d3c42b7}, - {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, - {0xa7f26836f282b732, 0x8e6cac7768d7141f}, - {0xd1ef0244af2364ff, 0x3207d795430cd927}, - {0x8335616aed761f1f, 0x7f44e6bd49e807b9}, - {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a7}, - {0xcd036837130890a1, 0x36dba887c37a8c10}, - {0x802221226be55a64, 0xc2494954da2c978a}, - {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6d}, - {0xc83553c5c8965d3d, 0x6f92829494e5acc8}, - {0xfa42a8b73abbf48c, 0xcb772339ba1f17fa}, - {0x9c69a97284b578d7, 0xff2a760414536efc}, - {0xc38413cf25e2d70d, 0xfef5138519684abb}, - {0xf46518c2ef5b8cd1, 0x7eb258665fc25d6a}, - {0x98bf2f79d5993802, 0xef2f773ffbd97a62}, - {0xbeeefb584aff8603, 0xaafb550ffacfd8fb}, - {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf39}, - {0x952ab45cfa97a0b2, 0xdd945a747bf26184}, - {0xba756174393d88df, 0x94f971119aeef9e5}, - {0xe912b9d1478ceb17, 0x7a37cd5601aab85e}, - {0x91abb422ccb812ee, 0xac62e055c10ab33b}, - {0xb616a12b7fe617aa, 0x577b986b314d600a}, - {0xe39c49765fdf9d94, 0xed5a7e85fda0b80c}, - {0x8e41ade9fbebc27d, 0x14588f13be847308}, - {0xb1d219647ae6b31c, 0x596eb2d8ae258fc9}, - {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bc}, - {0x8aec23d680043bee, 0x25de7bb9480d5855}, - {0xada72ccc20054ae9, 0xaf561aa79a10ae6b}, - {0xd910f7ff28069da4, 0x1b2ba1518094da05}, - {0x87aa9aff79042286, 0x90fb44d2f05d0843}, - {0xa99541bf57452b28, 0x353a1607ac744a54}, - {0xd3fa922f2d1675f2, 0x42889b8997915ce9}, - {0x847c9b5d7c2e09b7, 0x69956135febada12}, - {0xa59bc234db398c25, 0x43fab9837e699096}, - {0xcf02b2c21207ef2e, 0x94f967e45e03f4bc}, - {0x8161afb94b44f57d, 0x1d1be0eebac278f6}, - {0xa1ba1ba79e1632dc, 0x6462d92a69731733}, - {0xca28a291859bbf93, 0x7d7b8f7503cfdcff}, - {0xfcb2cb35e702af78, 0x5cda735244c3d43f}, - {0x9defbf01b061adab, 0x3a0888136afa64a8}, - {0xc56baec21c7a1916, 0x088aaa1845b8fdd1}, - {0xf6c69a72a3989f5b, 0x8aad549e57273d46}, - {0x9a3c2087a63f6399, 0x36ac54e2f678864c}, - {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7de}, - {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d6}, - {0x969eb7c47859e743, 0x9f644ae5a4b1b326}, - {0xbc4665b596706114, 0x873d5d9f0dde1fef}, - {0xeb57ff22fc0c7959, 0xa90cb506d155a7eb}, - {0x9316ff75dd87cbd8, 0x09a7f12442d588f3}, - {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30}, - {0xe5d3ef282a242e81, 0x8f1668c8a86da5fb}, - {0x8fa475791a569d10, 0xf96e017d694487bd}, - {0xb38d92d760ec4455, 0x37c981dcc395a9ad}, - {0xe070f78d3927556a, 0x85bbe253f47b1418}, - {0x8c469ab843b89562, 0x93956d7478ccec8f}, - {0xaf58416654a6babb, 0x387ac8d1970027b3}, - {0xdb2e51bfe9d0696a, 0x06997b05fcc0319f}, - {0x88fcf317f22241e2, 0x441fece3bdf81f04}, - {0xab3c2fddeeaad25a, 0xd527e81cad7626c4}, - {0xd60b3bd56a5586f1, 0x8a71e223d8d3b075}, - {0x85c7056562757456, 0xf6872d5667844e4a}, - {0xa738c6bebb12d16c, 0xb428f8ac016561dc}, - {0xd106f86e69d785c7, 0xe13336d701beba53}, - {0x82a45b450226b39c, 0xecc0024661173474}, - {0xa34d721642b06084, 0x27f002d7f95d0191}, - {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5}, - {0xff290242c83396ce, 0x7e67047175a15272}, - {0x9f79a169bd203e41, 0x0f0062c6e984d387}, - {0xc75809c42c684dd1, 0x52c07b78a3e60869}, - {0xf92e0c3537826145, 0xa7709a56ccdf8a83}, - {0x9bbcc7a142b17ccb, 0x88a66076400bb692}, - {0xc2abf989935ddbfe, 0x6acff893d00ea436}, - {0xf356f7ebf83552fe, 0x0583f6b8c4124d44}, - {0x98165af37b2153de, 0xc3727a337a8b704b}, - {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5d}, - {0xeda2ee1c7064130c, 0x1162def06f79df74}, - {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba9}, - {0xb9a74a0637ce2ee1, 0x6d953e2bd7173693}, - {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0438}, - {0x910ab1d4db9914a0, 0x1d9c9892400a22a3}, - {0xb54d5e4a127f59c8, 0x2503beb6d00cab4c}, - {0xe2a0b5dc971f303a, 0x2e44ae64840fd61e}, - {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, - {0xb10d8e1456105dad, 0x7425a83e872c5f48}, - {0xdd50f1996b947518, 0xd12f124e28f7771a}, - {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa70}, - {0xace73cbfdc0bfb7b, 0x636cc64d1001550c}, - {0xd8210befd30efa5a, 0x3c47f7e05401aa4f}, - {0x8714a775e3e95c78, 0x65acfaec34810a72}, - {0xa8d9d1535ce3b396, 0x7f1839a741a14d0e}, - {0xd31045a8341ca07c, 0x1ede48111209a051}, - {0x83ea2b892091e44d, 0x934aed0aab460433}, - {0xa4e4b66b68b65d60, 0xf81da84d56178540}, - {0xce1de40642e3f4b9, 0x36251260ab9d668f}, - {0x80d2ae83e9ce78f3, 0xc1d72b7c6b42601a}, - {0xa1075a24e4421730, 0xb24cf65b8612f820}, - {0xc94930ae1d529cfc, 0xdee033f26797b628}, - {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2}, - {0x9d412e0806e88aa5, 0x8e1f289560ee864f}, - {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e3}, - {0xf5b5d7ec8acb58a2, 0xae10af696774b1dc}, - {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef2a}, - {0xbff610b0cc6edd3f, 0x17fd090a58d32af4}, - {0xeff394dcff8a948e, 0xddfc4b4cef07f5b1}, - {0x95f83d0a1fb69cd9, 0x4abdaf101564f98f}, - {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f2}, - {0xea53df5fd18d5513, 0x84c86189216dc5ee}, - {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb5}, - {0xb7118682dbb66a77, 0x3fbc8c33221dc2a2}, - {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, - {0x8f05b1163ba6832d, 0x29cb4d87f2a7400f}, - {0xb2c71d5bca9023f8, 0x743e20e9ef511013}, - {0xdf78e4b2bd342cf6, 0x914da9246b255417}, - {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f}, - {0xae9672aba3d0c320, 0xa184ac2473b529b2}, - {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741f}, - {0x8865899617fb1871, 0x7e2fa67c7a658893}, - {0xaa7eebfb9df9de8d, 0xddbb901b98feeab8}, - {0xd51ea6fa85785631, 0x552a74227f3ea566}, - {0x8533285c936b35de, 0xd53a88958f872760}, - {0xa67ff273b8460356, 0x8a892abaf368f138}, - {0xd01fef10a657842c, 0x2d2b7569b0432d86}, - {0x8213f56a67f6b29b, 0x9c3b29620e29fc74}, - {0xa298f2c501f45f42, 0x8349f3ba91b47b90}, - {0xcb3f2f7642717713, 0x241c70a936219a74}, - {0xfe0efb53d30dd4d7, 0xed238cd383aa0111}, - {0x9ec95d1463e8a506, 0xf4363804324a40ab}, - {0xc67bb4597ce2ce48, 0xb143c6053edcd0d6}, - {0xf81aa16fdc1b81da, 0xdd94b7868e94050b}, - {0x9b10a4e5e9913128, 0xca7cf2b4191c8327}, - {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f1}, - {0xf24a01a73cf2dccf, 0xbc633b39673c8ced}, - {0x976e41088617ca01, 0xd5be0503e085d814}, - {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e19}, - {0xec9c459d51852ba2, 0xddf8e7d60ed1219f}, - {0x93e1ab8252f33b45, 0xcabb90e5c942b504}, - {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, - {0xe7109bfba19c0c9d, 0x0cc512670a783ad5}, - {0x906a617d450187e2, 0x27fb2b80668b24c6}, - {0xb484f9dc9641e9da, 0xb1f9f660802dedf7}, - {0xe1a63853bbd26451, 0x5e7873f8a0396974}, - {0x8d07e33455637eb2, 0xdb0b487b6423e1e9}, - {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda63}, - {0xdc5c5301c56b75f7, 0x7641a140cc7810fc}, - {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9e}, - {0xac2820d9623bf429, 0x546345fa9fbdcd45}, - {0xd732290fbacaf133, 0xa97c177947ad4096}, - {0x867f59a9d4bed6c0, 0x49ed8eabcccc485e}, - {0xa81f301449ee8c70, 0x5c68f256bfff5a75}, - {0xd226fc195c6a2f8c, 0x73832eec6fff3112}, - {0x83585d8fd9c25db7, 0xc831fd53c5ff7eac}, - {0xa42e74f3d032f525, 0xba3e7ca8b77f5e56}, - {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35ec}, - {0x80444b5e7aa7cf85, 0x7980d163cf5b81b4}, - {0xa0555e361951c366, 0xd7e105bcc3326220}, - {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa8}, - {0xfa856334878fc150, 0xb14f98f6f0feb952}, - {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d4}, - {0xc3b8358109e84f07, 0x0a862f80ec4700c9}, - {0xf4a642e14c6262c8, 0xcd27bb612758c0fb}, - {0x98e7e9cccfbd7dbd, 0x8038d51cb897789d}, - {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4}, - {0xeeea5d5004981478, 0x1858ccfce06cac75}, - {0x95527a5202df0ccb, 0x0f37801e0c43ebc9}, - {0xbaa718e68396cffd, 0xd30560258f54e6bb}, - {0xe950df20247c83fd, 0x47c6b82ef32a206a}, - {0x91d28b7416cdd27e, 0x4cdc331d57fa5442}, - {0xb6472e511c81471d, 0xe0133fe4adf8e953}, - {0xe3d8f9e563a198e5, 0x58180fddd97723a7}, - {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7649}, - {0xb201833b35d63f73, 0x2cd2cc6551e513db}, - {0xde81e40a034bcf4f, 0xf8077f7ea65e58d2}, - {0x8b112e86420f6191, 0xfb04afaf27faf783}, - {0xadd57a27d29339f6, 0x79c5db9af1f9b564}, - {0xd94ad8b1c7380874, 0x18375281ae7822bd}, - {0x87cec76f1c830548, 0x8f2293910d0b15b6}, - {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb23}, - {0xd433179d9c8cb841, 0x5fa60692a46151ec}, - {0x849feec281d7f328, 0xdbc7c41ba6bcd334}, - {0xa5c7ea73224deff3, 0x12b9b522906c0801}, - {0xcf39e50feae16bef, 0xd768226b34870a01}, - {0x81842f29f2cce375, 0xe6a1158300d46641}, - {0xa1e53af46f801c53, 0x60495ae3c1097fd1}, - {0xca5e89b18b602368, 0x385bb19cb14bdfc5}, - {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6}, - {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2}, - {0xc5a05277621be293, 0xc7098b7305241886}, - { 0xf70867153aa2db38, - 0xb8cbee4fc66d1ea8 } -#else - {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, - {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, - {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f}, - {0x86a8d39ef77164bc, 0xae5dff9c02033198}, - {0xd98ddaee19068c76, 0x3badd624dd9b0958}, - {0xafbd2350644eeacf, 0xe5d1929ef90898fb}, - {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2}, - {0xe55990879ddcaabd, 0xcc420a6a101d0516}, - {0xb94470938fa89bce, 0xf808e40e8d5b3e6a}, - {0x95a8637627989aad, 0xdde7001379a44aa9}, - {0xf1c90080baf72cb1, 0x5324c68b12dd6339}, - {0xc350000000000000, 0x0000000000000000}, - {0x9dc5ada82b70b59d, 0xf020000000000000}, - {0xfee50b7025c36a08, 0x02f236d04753d5b5}, - {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87}, - {0xa6539930bf6bff45, 0x84db8346b786151d}, - {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3}, - {0xd910f7ff28069da4, 0x1b2ba1518094da05}, - {0xaf58416654a6babb, 0x387ac8d1970027b3}, - {0x8da471a9de737e24, 0x5ceaecfed289e5d3}, - {0xe4d5e82392a40515, 0x0fabaf3feaa5334b}, - {0xb8da1662e7b00a17, 0x3d6a751f3b936244}, - { 0x95527a5202df0ccb, - 0x0f37801e0c43ebc9 } -#endif - }; - -#if FMT_USE_FULL_CACHE_DRAGONBOX - return pow10_significands[k - float_info::min_k]; -#else - static constexpr const uint64_t powers_of_5_64[] = { - 0x0000000000000001, 0x0000000000000005, 0x0000000000000019, - 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, - 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, - 0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd, - 0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9, - 0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5, - 0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631, - 0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed, - 0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9}; - - static const int compression_ratio = 27; - - // Compute base index. - int cache_index = (k - float_info::min_k) / compression_ratio; - int kb = cache_index * compression_ratio + float_info::min_k; - int offset = k - kb; - - // Get base cache. - uint128_fallback base_cache = pow10_significands[cache_index]; - if (offset == 0) return base_cache; - - // Compute the required amount of bit-shift. - int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset; - FMT_ASSERT(alpha > 0 && alpha < 64, "shifting error detected"); - - // Try to recover the real cache. - uint64_t pow5 = powers_of_5_64[offset]; - uint128_fallback recovered_cache = umul128(base_cache.high(), pow5); - uint128_fallback middle_low = umul128(base_cache.low(), pow5); - - recovered_cache += middle_low.high(); - - uint64_t high_to_middle = recovered_cache.high() << (64 - alpha); - uint64_t middle_to_low = recovered_cache.low() << (64 - alpha); - - recovered_cache = - uint128_fallback{(recovered_cache.low() >> alpha) | high_to_middle, - ((middle_low.low() >> alpha) | middle_to_low)}; - FMT_ASSERT(recovered_cache.low() + 1 != 0, ""); - return {recovered_cache.high(), recovered_cache.low() + 1}; -#endif - } - - struct compute_mul_result { - carrier_uint result; - bool is_integer; - }; - struct compute_mul_parity_result { - bool parity; - bool is_integer; - }; - - static compute_mul_result compute_mul( - carrier_uint u, const cache_entry_type& cache) noexcept { - auto r = umul192_upper128(u, cache); - return {r.high(), r.low() == 0}; - } - - static uint32_t compute_delta(cache_entry_type const& cache, - int beta) noexcept { - return static_cast(cache.high() >> (64 - 1 - beta)); - } - - static compute_mul_parity_result compute_mul_parity( - carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept { - FMT_ASSERT(beta >= 1, ""); - FMT_ASSERT(beta < 64, ""); - - auto r = umul192_lower128(two_f, cache); - return {((r.high() >> (64 - beta)) & 1) != 0, - ((r.high() << beta) | (r.low() >> (64 - beta))) == 0}; - } - - static carrier_uint compute_left_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { - return (cache.high() - - (cache.high() >> (num_significand_bits() + 2))) >> - (64 - num_significand_bits() - 1 - beta); - } - - static carrier_uint compute_right_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { - return (cache.high() + - (cache.high() >> (num_significand_bits() + 1))) >> - (64 - num_significand_bits() - 1 - beta); - } - - static carrier_uint compute_round_up_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { - return ((cache.high() >> (64 - num_significand_bits() - 2 - beta)) + - 1) / - 2; - } -}; - -// Various integer checks -template -bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { - const int case_shorter_interval_left_endpoint_lower_threshold = 2; - const int case_shorter_interval_left_endpoint_upper_threshold = 3; - return exponent >= case_shorter_interval_left_endpoint_lower_threshold && - exponent <= case_shorter_interval_left_endpoint_upper_threshold; -} - -// Remove trailing zeros from n and return the number of zeros removed (float) -FMT_INLINE int remove_trailing_zeros(uint32_t& n) noexcept { - FMT_ASSERT(n != 0, ""); - const uint32_t mod_inv_5 = 0xcccccccd; - const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5; - - int s = 0; - while (true) { - auto q = rotr(n * mod_inv_25, 2); - if (q > max_value() / 100) break; - n = q; - s += 2; - } - auto q = rotr(n * mod_inv_5, 1); - if (q <= max_value() / 10) { - n = q; - s |= 1; - } - - return s; -} - -// Removes trailing zeros and returns the number of zeros removed (double) -FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { - FMT_ASSERT(n != 0, ""); - - // This magic number is ceil(2^90 / 10^8). - constexpr uint64_t magic_number = 12379400392853802749ull; - auto nm = umul128(n, magic_number); - - // Is n is divisible by 10^8? - if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) { - // If yes, work with the quotient. - auto n32 = static_cast(nm.high() >> (90 - 64)); - - const uint32_t mod_inv_5 = 0xcccccccd; - const uint32_t mod_inv_25 = mod_inv_5 * mod_inv_5; - - int s = 8; - while (true) { - auto q = rotr(n32 * mod_inv_25, 2); - if (q > max_value() / 100) break; - n32 = q; - s += 2; - } - auto q = rotr(n32 * mod_inv_5, 1); - if (q <= max_value() / 10) { - n32 = q; - s |= 1; - } - - n = n32; - return s; - } - - // If n is not divisible by 10^8, work with n itself. - const uint64_t mod_inv_5 = 0xcccccccccccccccd; - const uint64_t mod_inv_25 = mod_inv_5 * mod_inv_5; - - int s = 0; - while (true) { - auto q = rotr(n * mod_inv_25, 2); - if (q > max_value() / 100) break; - n = q; - s += 2; - } - auto q = rotr(n * mod_inv_5, 1); - if (q <= max_value() / 10) { - n = q; - s |= 1; - } - - return s; -} - -// The main algorithm for shorter interval case -template -FMT_INLINE decimal_fp shorter_interval_case(int exponent) noexcept { - decimal_fp ret_value; - // Compute k and beta - const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent); - const int beta = exponent + floor_log2_pow10(-minus_k); - - // Compute xi and zi - using cache_entry_type = typename cache_accessor::cache_entry_type; - const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); - - auto xi = cache_accessor::compute_left_endpoint_for_shorter_interval_case( - cache, beta); - auto zi = cache_accessor::compute_right_endpoint_for_shorter_interval_case( - cache, beta); - - // If the left endpoint is not an integer, increase it - if (!is_left_endpoint_integer_shorter_interval(exponent)) ++xi; - - // Try bigger divisor - ret_value.significand = zi / 10; - - // If succeed, remove trailing zeros if necessary and return - if (ret_value.significand * 10 >= xi) { - ret_value.exponent = minus_k + 1; - ret_value.exponent += remove_trailing_zeros(ret_value.significand); - return ret_value; - } - - // Otherwise, compute the round-up of y - ret_value.significand = - cache_accessor::compute_round_up_for_shorter_interval_case(cache, - beta); - ret_value.exponent = minus_k; - - // When tie occurs, choose one of them according to the rule - if (exponent >= float_info::shorter_interval_tie_lower_threshold && - exponent <= float_info::shorter_interval_tie_upper_threshold) { - ret_value.significand = ret_value.significand % 2 == 0 - ? ret_value.significand - : ret_value.significand - 1; - } else if (ret_value.significand < xi) { - ++ret_value.significand; - } - return ret_value; -} - -template decimal_fp to_decimal(T x) noexcept { - // Step 1: integer promotion & Schubfach multiplier calculation. - - using carrier_uint = typename float_info::carrier_uint; - using cache_entry_type = typename cache_accessor::cache_entry_type; - auto br = bit_cast(x); - - // Extract significand bits and exponent bits. - const carrier_uint significand_mask = - (static_cast(1) << num_significand_bits()) - 1; - carrier_uint significand = (br & significand_mask); - int exponent = - static_cast((br & exponent_mask()) >> num_significand_bits()); - - if (exponent != 0) { // Check if normal. - exponent -= exponent_bias() + num_significand_bits(); - - // Shorter interval case; proceed like Schubfach. - // In fact, when exponent == 1 and significand == 0, the interval is - // regular. However, it can be shown that the end-results are anyway same. - if (significand == 0) return shorter_interval_case(exponent); - - significand |= (static_cast(1) << num_significand_bits()); - } else { - // Subnormal case; the interval is always regular. - if (significand == 0) return {0, 0}; - exponent = - std::numeric_limits::min_exponent - num_significand_bits() - 1; - } - - const bool include_left_endpoint = (significand % 2 == 0); - const bool include_right_endpoint = include_left_endpoint; - - // Compute k and beta. - const int minus_k = floor_log10_pow2(exponent) - float_info::kappa; - const cache_entry_type cache = cache_accessor::get_cached_power(-minus_k); - const int beta = exponent + floor_log2_pow10(-minus_k); - - // Compute zi and deltai. - // 10^kappa <= deltai < 10^(kappa + 1) - const uint32_t deltai = cache_accessor::compute_delta(cache, beta); - const carrier_uint two_fc = significand << 1; - - // For the case of binary32, the result of integer check is not correct for - // 29711844 * 2^-82 - // = 6.1442653300000000008655037797566933477355632930994033813476... * 10^-18 - // and 29711844 * 2^-81 - // = 1.2288530660000000001731007559513386695471126586198806762695... * 10^-17, - // and they are the unique counterexamples. However, since 29711844 is even, - // this does not cause any problem for the endpoints calculations; it can only - // cause a problem when we need to perform integer check for the center. - // Fortunately, with these inputs, that branch is never executed, so we are - // fine. - const typename cache_accessor::compute_mul_result z_mul = - cache_accessor::compute_mul((two_fc | 1) << beta, cache); - - // Step 2: Try larger divisor; remove trailing zeros if necessary. - - // Using an upper bound on zi, we might be able to optimize the division - // better than the compiler; we are computing zi / big_divisor here. - decimal_fp ret_value; - ret_value.significand = divide_by_10_to_kappa_plus_1(z_mul.result); - uint32_t r = static_cast(z_mul.result - float_info::big_divisor * - ret_value.significand); - - if (r < deltai) { - // Exclude the right endpoint if necessary. - if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) { - --ret_value.significand; - r = float_info::big_divisor; - goto small_divisor_case_label; - } - } else if (r > deltai) { - goto small_divisor_case_label; - } else { - // r == deltai; compare fractional parts. - const typename cache_accessor::compute_mul_parity_result x_mul = - cache_accessor::compute_mul_parity(two_fc - 1, cache, beta); - - if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint))) - goto small_divisor_case_label; - } - ret_value.exponent = minus_k + float_info::kappa + 1; - - // We may need to remove trailing zeros. - ret_value.exponent += remove_trailing_zeros(ret_value.significand); - return ret_value; - - // Step 3: Find the significand with the smaller divisor. - -small_divisor_case_label: - ret_value.significand *= 10; - ret_value.exponent = minus_k + float_info::kappa; - - uint32_t dist = r - (deltai / 2) + (float_info::small_divisor / 2); - const bool approx_y_parity = - ((dist ^ (float_info::small_divisor / 2)) & 1) != 0; - - // Is dist divisible by 10^kappa? - const bool divisible_by_small_divisor = - check_divisibility_and_divide_by_pow10::kappa>(dist); - - // Add dist / 10^kappa to the significand. - ret_value.significand += dist; - - if (!divisible_by_small_divisor) return ret_value; - - // Check z^(f) >= epsilon^(f). - // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1, - // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f). - // Since there are only 2 possibilities, we only need to care about the - // parity. Also, zi and r should have the same parity since the divisor - // is an even number. - const auto y_mul = cache_accessor::compute_mul_parity(two_fc, cache, beta); - - // If z^(f) >= epsilon^(f), we might have a tie when z^(f) == epsilon^(f), - // or equivalently, when y is an integer. - if (y_mul.parity != approx_y_parity) - --ret_value.significand; - else if (y_mul.is_integer & (ret_value.significand % 2 != 0)) - --ret_value.significand; - return ret_value; -} -} // namespace dragonbox - -#ifdef _MSC_VER -FMT_FUNC auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...) - -> int { - auto args = va_list(); - va_start(args, fmt); - int result = vsnprintf_s(buf, size, _TRUNCATE, fmt, args); - va_end(args); - return result; -} -#endif -} // namespace detail - -template <> struct formatter { - FMT_CONSTEXPR auto parse(format_parse_context& ctx) - -> format_parse_context::iterator { - return ctx.begin(); - } - - template - auto format(const detail::bigint& n, FormatContext& ctx) const -> - typename FormatContext::iterator { - auto out = ctx.out(); - bool first = true; - for (auto i = n.bigits_.size(); i > 0; --i) { - auto value = n.bigits_[i - 1u]; - if (first) { - out = format_to(out, FMT_STRING("{:x}"), value); - first = false; - continue; - } - out = format_to(out, FMT_STRING("{:08x}"), value); - } - if (n.exp_ > 0) - out = format_to(out, FMT_STRING("p{}"), - n.exp_ * detail::bigint::bigit_bits); - return out; - } -}; - -FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { - for_each_codepoint(s, [this](uint32_t cp, string_view) { - if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8")); - if (cp <= 0xFFFF) { - buffer_.push_back(static_cast(cp)); - } else { - cp -= 0x10000; - buffer_.push_back(static_cast(0xD800 + (cp >> 10))); - buffer_.push_back(static_cast(0xDC00 + (cp & 0x3FF))); - } - return true; - }); - buffer_.push_back(0); -} - -FMT_FUNC void format_system_error(detail::buffer& out, int error_code, - const char* message) noexcept { - FMT_TRY { - auto ec = std::error_code(error_code, std::generic_category()); - write(std::back_inserter(out), std::system_error(ec, message).what()); - return; - } - FMT_CATCH(...) {} - format_error_code(out, error_code, message); -} - -FMT_FUNC void report_system_error(int error_code, - const char* message) noexcept { - report_error(format_system_error, error_code, message); -} - -FMT_FUNC std::string vformat(string_view fmt, format_args args) { - // Don't optimize the "{}" case to keep the binary size small and because it - // can be better optimized in fmt::format anyway. - auto buffer = memory_buffer(); - detail::vformat_to(buffer, fmt, args); - return to_string(buffer); -} - -namespace detail { -#ifdef _WIN32 -using dword = conditional_t; -extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // - void*, const void*, dword, dword*, void*); - -FMT_FUNC bool write_console(std::FILE* f, string_view text) { - auto fd = _fileno(f); - if (_isatty(fd)) { - detail::utf8_to_utf16 u16(string_view(text.data(), text.size())); - auto written = detail::dword(); - if (detail::WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), - u16.c_str(), static_cast(u16.size()), - &written, nullptr)) { - return true; - } - } - // We return false if the file descriptor was not TTY, or it was but - // SetConsoleW failed which can happen if the output has been redirected to - // NUL. In both cases when we return false, we should attempt to do regular - // write via fwrite or std::ostream::write. - return false; -} -#endif - -FMT_FUNC void print(std::FILE* f, string_view text) { -#ifdef _WIN32 - if (write_console(f, text)) return; -#endif - detail::fwrite_fully(text.data(), 1, text.size(), f); -} -} // namespace detail - -FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { - memory_buffer buffer; - detail::vformat_to(buffer, format_str, args); - detail::print(f, {buffer.data(), buffer.size()}); -} - -#ifdef _WIN32 -// Print assuming legacy (non-Unicode) encoding. -FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str, - format_args args) { - memory_buffer buffer; - detail::vformat_to(buffer, format_str, - basic_format_args>(args)); - fwrite_fully(buffer.data(), 1, buffer.size(), f); -} -#endif - -FMT_FUNC void vprint(string_view format_str, format_args args) { - vprint(stdout, format_str, args); -} - -namespace detail { - -struct singleton { - unsigned char upper; - unsigned char lower_count; -}; - -inline auto is_printable(uint16_t x, const singleton* singletons, - size_t singletons_size, - const unsigned char* singleton_lowers, - const unsigned char* normal, size_t normal_size) - -> bool { - auto upper = x >> 8; - auto lower_start = 0; - for (size_t i = 0; i < singletons_size; ++i) { - auto s = singletons[i]; - auto lower_end = lower_start + s.lower_count; - if (upper < s.upper) break; - if (upper == s.upper) { - for (auto j = lower_start; j < lower_end; ++j) { - if (singleton_lowers[j] == (x & 0xff)) return false; - } - } - lower_start = lower_end; - } - - auto xsigned = static_cast(x); - auto current = true; - for (size_t i = 0; i < normal_size; ++i) { - auto v = static_cast(normal[i]); - auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v; - xsigned -= len; - if (xsigned < 0) break; - current = !current; - } - return current; -} - -// This code is generated by support/printable.py. -FMT_FUNC auto is_printable(uint32_t cp) -> bool { - static constexpr singleton singletons0[] = { - {0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8}, - {0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13}, - {0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5}, - {0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22}, - {0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3}, - {0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8}, - {0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9}, - }; - static constexpr unsigned char singletons0_lower[] = { - 0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90, - 0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f, - 0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1, - 0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04, - 0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d, - 0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf, - 0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, - 0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d, - 0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d, - 0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d, - 0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5, - 0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7, - 0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49, - 0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7, - 0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7, - 0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e, - 0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16, - 0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e, - 0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f, - 0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf, - 0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0, - 0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27, - 0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91, - 0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7, - 0xfe, 0xff, - }; - static constexpr singleton singletons1[] = { - {0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2}, - {0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5}, - {0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5}, - {0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2}, - {0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5}, - {0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2}, - {0xfa, 2}, {0xfb, 1}, - }; - static constexpr unsigned char singletons1_lower[] = { - 0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07, - 0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36, - 0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87, - 0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a, - 0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b, - 0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9, - 0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66, - 0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27, - 0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc, - 0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7, - 0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6, - 0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c, - 0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66, - 0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0, - 0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93, - }; - static constexpr unsigned char normal0[] = { - 0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04, - 0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0, - 0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01, - 0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03, - 0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03, - 0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a, - 0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15, - 0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f, - 0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80, - 0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07, - 0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06, - 0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04, - 0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac, - 0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c, - 0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11, - 0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c, - 0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b, - 0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6, - 0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03, - 0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80, - 0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06, - 0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c, - 0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17, - 0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80, - 0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80, - 0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d, - }; - static constexpr unsigned char normal1[] = { - 0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f, - 0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e, - 0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04, - 0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09, - 0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16, - 0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f, - 0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36, - 0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33, - 0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08, - 0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e, - 0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41, - 0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03, - 0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22, - 0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04, - 0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45, - 0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03, - 0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81, - 0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75, - 0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1, - 0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a, - 0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11, - 0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09, - 0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89, - 0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6, - 0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09, - 0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50, - 0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05, - 0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83, - 0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05, - 0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80, - 0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80, - 0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07, - 0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e, - 0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07, - 0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06, - }; - auto lower = static_cast(cp); - if (cp < 0x10000) { - return is_printable(lower, singletons0, - sizeof(singletons0) / sizeof(*singletons0), - singletons0_lower, normal0, sizeof(normal0)); - } - if (cp < 0x20000) { - return is_printable(lower, singletons1, - sizeof(singletons1) / sizeof(*singletons1), - singletons1_lower, normal1, sizeof(normal1)); - } - if (0x2a6de <= cp && cp < 0x2a700) return false; - if (0x2b735 <= cp && cp < 0x2b740) return false; - if (0x2b81e <= cp && cp < 0x2b820) return false; - if (0x2cea2 <= cp && cp < 0x2ceb0) return false; - if (0x2ebe1 <= cp && cp < 0x2f800) return false; - if (0x2fa1e <= cp && cp < 0x30000) return false; - if (0x3134b <= cp && cp < 0xe0100) return false; - if (0xe01f0 <= cp && cp < 0x110000) return false; - return cp < 0x110000; -} - -} // namespace detail - -FMT_END_NAMESPACE - -#endif // FMT_FORMAT_INL_H_ diff --git a/Externals/fmt/include/fmt/format.h b/Externals/fmt/include/fmt/format.h deleted file mode 100755 index fef5a5dbf9..0000000000 --- a/Externals/fmt/include/fmt/format.h +++ /dev/null @@ -1,4217 +0,0 @@ -/* - Formatting library for C++ - - Copyright (c) 2012 - present, Victor Zverovich - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the - "Software"), to deal in the Software without restriction, including - without limitation the rights to use, copy, modify, merge, publish, - distribute, sublicense, and/or sell copies of the Software, and to - permit persons to whom the Software is furnished to do so, subject to - the following conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - --- Optional exception to the license --- - - As an exception, if, as a result of your compiling your source code, portions - of this Software are embedded into a machine-executable object form of such - source code, you may redistribute such embedded portions in such object form - without including the above copyright and permission notices. - */ - -#ifndef FMT_FORMAT_H_ -#define FMT_FORMAT_H_ - -#include // std::signbit -#include // uint32_t -#include // std::memcpy -#include // std::numeric_limits -#include // std::uninitialized_copy -#include // std::runtime_error -#include // std::system_error - -#ifdef __cpp_lib_bit_cast -# include // std::bitcast -#endif - -#include "core.h" - -#if FMT_GCC_VERSION -# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) -#else -# define FMT_GCC_VISIBILITY_HIDDEN -#endif - -#ifdef __NVCC__ -# define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) -#else -# define FMT_CUDA_VERSION 0 -#endif - -#ifdef __has_builtin -# define FMT_HAS_BUILTIN(x) __has_builtin(x) -#else -# define FMT_HAS_BUILTIN(x) 0 -#endif - -#if FMT_GCC_VERSION || FMT_CLANG_VERSION -# define FMT_NOINLINE __attribute__((noinline)) -#else -# define FMT_NOINLINE -#endif - -#if FMT_MSC_VERSION -# define FMT_MSC_DEFAULT = default -#else -# define FMT_MSC_DEFAULT -#endif - -#ifndef FMT_THROW -# if FMT_EXCEPTIONS -# if FMT_MSC_VERSION || defined(__NVCC__) -FMT_BEGIN_NAMESPACE -namespace detail { -template inline void do_throw(const Exception& x) { - // Silence unreachable code warnings in MSVC and NVCC because these - // are nearly impossible to fix in a generic code. - volatile bool b = true; - if (b) throw x; -} -} // namespace detail -FMT_END_NAMESPACE -# define FMT_THROW(x) detail::do_throw(x) -# else -# define FMT_THROW(x) throw x -# endif -# else -# define FMT_THROW(x) \ - do { \ - FMT_ASSERT(false, (x).what()); \ - } while (false) -# endif -#endif - -#if FMT_EXCEPTIONS -# define FMT_TRY try -# define FMT_CATCH(x) catch (x) -#else -# define FMT_TRY if (true) -# define FMT_CATCH(x) if (false) -#endif - -#ifndef FMT_MAYBE_UNUSED -# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) -# define FMT_MAYBE_UNUSED [[maybe_unused]] -# else -# define FMT_MAYBE_UNUSED -# endif -#endif - -#ifndef FMT_USE_USER_DEFINED_LITERALS -// EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs. -# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ - FMT_MSC_VERSION >= 1900) && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480) -# define FMT_USE_USER_DEFINED_LITERALS 1 -# else -# define FMT_USE_USER_DEFINED_LITERALS 0 -# endif -#endif - -// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of -// integer formatter template instantiations to just one by only using the -// largest integer type. This results in a reduction in binary size but will -// cause a decrease in integer formatting performance. -#if !defined(FMT_REDUCE_INT_INSTANTIATIONS) -# define FMT_REDUCE_INT_INSTANTIATIONS 0 -#endif - -// __builtin_clz is broken in clang with Microsoft CodeGen: -// https://github.com/fmtlib/fmt/issues/519. -#if !FMT_MSC_VERSION -# if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION -# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) -# endif -# if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION -# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) -# endif -#endif - -// __builtin_ctz is broken in Intel Compiler Classic on Windows: -// https://github.com/fmtlib/fmt/issues/2510. -#ifndef __ICL -# if FMT_HAS_BUILTIN(__builtin_ctz) || FMT_GCC_VERSION || FMT_ICC_VERSION || \ - defined(__NVCOMPILER) -# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) -# endif -# if FMT_HAS_BUILTIN(__builtin_ctzll) || FMT_GCC_VERSION || \ - FMT_ICC_VERSION || defined(__NVCOMPILER) -# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) -# endif -#endif - -#if FMT_MSC_VERSION -# include // _BitScanReverse[64], _BitScanForward[64], _umul128 -#endif - -// Some compilers masquerade as both MSVC and GCC-likes or otherwise support -// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the -// MSVC intrinsics if the clz and clzll builtins are not available. -#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) && \ - !defined(FMT_BUILTIN_CTZLL) -FMT_BEGIN_NAMESPACE -namespace detail { -// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. -# if !defined(__clang__) -# pragma intrinsic(_BitScanForward) -# pragma intrinsic(_BitScanReverse) -# if defined(_WIN64) -# pragma intrinsic(_BitScanForward64) -# pragma intrinsic(_BitScanReverse64) -# endif -# endif - -inline auto clz(uint32_t x) -> int { - unsigned long r = 0; - _BitScanReverse(&r, x); - FMT_ASSERT(x != 0, ""); - // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, - // which the callers guarantee to not happen. - FMT_MSC_WARNING(suppress : 6102) - return 31 ^ static_cast(r); -} -# define FMT_BUILTIN_CLZ(n) detail::clz(n) - -inline auto clzll(uint64_t x) -> int { - unsigned long r = 0; -# ifdef _WIN64 - _BitScanReverse64(&r, x); -# else - // Scan the high 32 bits. - if (_BitScanReverse(&r, static_cast(x >> 32))) return 63 ^ (r + 32); - // Scan the low 32 bits. - _BitScanReverse(&r, static_cast(x)); -# endif - FMT_ASSERT(x != 0, ""); - FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. - return 63 ^ static_cast(r); -} -# define FMT_BUILTIN_CLZLL(n) detail::clzll(n) - -inline auto ctz(uint32_t x) -> int { - unsigned long r = 0; - _BitScanForward(&r, x); - FMT_ASSERT(x != 0, ""); - FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. - return static_cast(r); -} -# define FMT_BUILTIN_CTZ(n) detail::ctz(n) - -inline auto ctzll(uint64_t x) -> int { - unsigned long r = 0; - FMT_ASSERT(x != 0, ""); - FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. -# ifdef _WIN64 - _BitScanForward64(&r, x); -# else - // Scan the low 32 bits. - if (_BitScanForward(&r, static_cast(x))) return static_cast(r); - // Scan the high 32 bits. - _BitScanForward(&r, static_cast(x >> 32)); - r += 32; -# endif - return static_cast(r); -} -# define FMT_BUILTIN_CTZLL(n) detail::ctzll(n) -} // namespace detail -FMT_END_NAMESPACE -#endif - -FMT_BEGIN_NAMESPACE -namespace detail { - -FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { - ignore_unused(condition); -#ifdef FMT_FUZZ - if (condition) throw std::runtime_error("fuzzing limit reached"); -#endif -} - -template struct string_literal { - static constexpr CharT value[sizeof...(C)] = {C...}; - constexpr operator basic_string_view() const { - return {value, sizeof...(C)}; - } -}; - -#if FMT_CPLUSPLUS < 201703L -template -constexpr CharT string_literal::value[sizeof...(C)]; -#endif - -template class formatbuf : public Streambuf { - private: - using char_type = typename Streambuf::char_type; - using streamsize = decltype(std::declval().sputn(nullptr, 0)); - using int_type = typename Streambuf::int_type; - using traits_type = typename Streambuf::traits_type; - - buffer& buffer_; - - public: - explicit formatbuf(buffer& buf) : buffer_(buf) {} - - protected: - // The put area is always empty. This makes the implementation simpler and has - // the advantage that the streambuf and the buffer are always in sync and - // sputc never writes into uninitialized memory. A disadvantage is that each - // call to sputc always results in a (virtual) call to overflow. There is no - // disadvantage here for sputn since this always results in a call to xsputn. - - auto overflow(int_type ch) -> int_type override { - if (!traits_type::eq_int_type(ch, traits_type::eof())) - buffer_.push_back(static_cast(ch)); - return ch; - } - - auto xsputn(const char_type* s, streamsize count) -> streamsize override { - buffer_.append(s, s + count); - return count; - } -}; - -// Implementation of std::bit_cast for pre-C++20. -template -FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { -#ifdef __cpp_lib_bit_cast - if (is_constant_evaluated()) return std::bit_cast(from); -#endif - auto to = To(); - // The cast suppresses a bogus -Wclass-memaccess on GCC. - std::memcpy(static_cast(&to), &from, sizeof(to)); - return to; -} - -inline auto is_big_endian() -> bool { -#ifdef _WIN32 - return false; -#elif defined(__BIG_ENDIAN__) - return true; -#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) - return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__; -#else - struct bytes { - char data[sizeof(int)]; - }; - return bit_cast(1).data[0] == 0; -#endif -} - -class uint128_fallback { - private: - uint64_t lo_, hi_; - - friend uint128_fallback umul128(uint64_t x, uint64_t y) noexcept; - - public: - constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} - constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} - - constexpr uint64_t high() const noexcept { return hi_; } - constexpr uint64_t low() const noexcept { return lo_; } - - template ::value)> - constexpr explicit operator T() const { - return static_cast(lo_); - } - - friend constexpr auto operator==(const uint128_fallback& lhs, - const uint128_fallback& rhs) -> bool { - return lhs.hi_ == rhs.hi_ && lhs.lo_ == rhs.lo_; - } - friend constexpr auto operator!=(const uint128_fallback& lhs, - const uint128_fallback& rhs) -> bool { - return !(lhs == rhs); - } - friend constexpr auto operator>(const uint128_fallback& lhs, - const uint128_fallback& rhs) -> bool { - return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_; - } - friend constexpr auto operator|(const uint128_fallback& lhs, - const uint128_fallback& rhs) - -> uint128_fallback { - return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_}; - } - friend constexpr auto operator&(const uint128_fallback& lhs, - const uint128_fallback& rhs) - -> uint128_fallback { - return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_}; - } - friend auto operator+(const uint128_fallback& lhs, - const uint128_fallback& rhs) -> uint128_fallback { - auto result = uint128_fallback(lhs); - result += rhs; - return result; - } - friend auto operator*(const uint128_fallback& lhs, uint32_t rhs) - -> uint128_fallback { - FMT_ASSERT(lhs.hi_ == 0, ""); - uint64_t hi = (lhs.lo_ >> 32) * rhs; - uint64_t lo = (lhs.lo_ & ~uint32_t()) * rhs; - uint64_t new_lo = (hi << 32) + lo; - return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; - } - friend auto operator-(const uint128_fallback& lhs, uint64_t rhs) - -> uint128_fallback { - return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; - } - FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback { - if (shift == 64) return {0, hi_}; - if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64); - return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)}; - } - FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback { - if (shift == 64) return {lo_, 0}; - if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64); - return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)}; - } - FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& { - return *this = *this >> shift; - } - FMT_CONSTEXPR void operator+=(uint128_fallback n) { - uint64_t new_lo = lo_ + n.lo_; - uint64_t new_hi = hi_ + n.hi_ + (new_lo < lo_ ? 1 : 0); - FMT_ASSERT(new_hi >= hi_, ""); - lo_ = new_lo; - hi_ = new_hi; - } - - FMT_CONSTEXPR20 uint128_fallback& operator+=(uint64_t n) noexcept { - if (is_constant_evaluated()) { - lo_ += n; - hi_ += (lo_ < n ? 1 : 0); - return *this; - } -#if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__) - unsigned long long carry; - lo_ = __builtin_addcll(lo_, n, 0, &carry); - hi_ += carry; -#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__) - unsigned long long result; - auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result); - lo_ = result; - hi_ += carry; -#elif defined(_MSC_VER) && defined(_M_X64) - auto carry = _addcarry_u64(0, lo_, n, &lo_); - _addcarry_u64(carry, hi_, 0, &hi_); -#else - lo_ += n; - hi_ += (lo_ < n ? 1 : 0); -#endif - return *this; - } -}; - -using uint128_t = conditional_t; - -#ifdef UINTPTR_MAX -using uintptr_t = ::uintptr_t; -#else -using uintptr_t = uint128_t; -#endif - -// Returns the largest possible value for type T. Same as -// std::numeric_limits::max() but shorter and not affected by the max macro. -template constexpr auto max_value() -> T { - return (std::numeric_limits::max)(); -} -template constexpr auto num_bits() -> int { - return std::numeric_limits::digits; -} -// std::numeric_limits::digits may return 0 for 128-bit ints. -template <> constexpr auto num_bits() -> int { return 128; } -template <> constexpr auto num_bits() -> int { return 128; } - -// A heterogeneous bit_cast used for converting 96-bit long double to uint128_t -// and 128-bit pointers to uint128_fallback. -template sizeof(From))> -inline auto bit_cast(const From& from) -> To { - constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned)); - struct data_t { - unsigned value[static_cast(size)]; - } data = bit_cast(from); - auto result = To(); - if (const_check(is_big_endian())) { - for (int i = 0; i < size; ++i) - result = (result << num_bits()) | data.value[i]; - } else { - for (int i = size - 1; i >= 0; --i) - result = (result << num_bits()) | data.value[i]; - } - return result; -} - -FMT_INLINE void assume(bool condition) { - (void)condition; -#if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION - __builtin_assume(condition); -#endif -} - -// An approximation of iterator_t for pre-C++20 systems. -template -using iterator_t = decltype(std::begin(std::declval())); -template using sentinel_t = decltype(std::end(std::declval())); - -// A workaround for std::string not having mutable data() until C++17. -template -inline auto get_data(std::basic_string& s) -> Char* { - return &s[0]; -} -template -inline auto get_data(Container& c) -> typename Container::value_type* { - return c.data(); -} - -#if defined(_SECURE_SCL) && _SECURE_SCL -// Make a checked iterator to avoid MSVC warnings. -template using checked_ptr = stdext::checked_array_iterator; -template -constexpr auto make_checked(T* p, size_t size) -> checked_ptr { - return {p, size}; -} -#else -template using checked_ptr = T*; -template constexpr auto make_checked(T* p, size_t) -> T* { - return p; -} -#endif - -// Attempts to reserve space for n extra characters in the output range. -// Returns a pointer to the reserved range or a reference to it. -template ::value)> -#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION -__attribute__((no_sanitize("undefined"))) -#endif -inline auto -reserve(std::back_insert_iterator it, size_t n) - -> checked_ptr { - Container& c = get_container(it); - size_t size = c.size(); - c.resize(size + n); - return make_checked(get_data(c) + size, n); -} - -template -inline auto reserve(buffer_appender it, size_t n) -> buffer_appender { - buffer& buf = get_container(it); - buf.try_reserve(buf.size() + n); - return it; -} - -template -constexpr auto reserve(Iterator& it, size_t) -> Iterator& { - return it; -} - -template -using reserve_iterator = - remove_reference_t(), 0))>; - -template -constexpr auto to_pointer(OutputIt, size_t) -> T* { - return nullptr; -} -template auto to_pointer(buffer_appender it, size_t n) -> T* { - buffer& buf = get_container(it); - auto size = buf.size(); - if (buf.capacity() < size + n) return nullptr; - buf.try_resize(size + n); - return buf.data() + size; -} - -template ::value)> -inline auto base_iterator(std::back_insert_iterator& it, - checked_ptr) - -> std::back_insert_iterator { - return it; -} - -template -constexpr auto base_iterator(Iterator, Iterator it) -> Iterator { - return it; -} - -// is spectacularly slow to compile in C++20 so use a simple fill_n -// instead (#1998). -template -FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) - -> OutputIt { - for (Size i = 0; i < count; ++i) *out++ = value; - return out; -} -template -FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { - if (is_constant_evaluated()) { - return fill_n(out, count, value); - } - std::memset(out, value, to_unsigned(count)); - return out + count; -} - -#ifdef __cpp_char8_t -using char8_type = char8_t; -#else -enum char8_type : unsigned char {}; -#endif - -template -FMT_CONSTEXPR FMT_NOINLINE auto copy_str_noinline(InputIt begin, InputIt end, - OutputIt out) -> OutputIt { - return copy_str(begin, end, out); -} - -// A public domain branchless UTF-8 decoder by Christopher Wellons: -// https://github.com/skeeto/branchless-utf8 -/* Decode the next character, c, from s, reporting errors in e. - * - * Since this is a branchless decoder, four bytes will be read from the - * buffer regardless of the actual length of the next character. This - * means the buffer _must_ have at least three bytes of zero padding - * following the end of the data stream. - * - * Errors are reported in e, which will be non-zero if the parsed - * character was somehow invalid: invalid byte sequence, non-canonical - * encoding, or a surrogate half. - * - * The function returns a pointer to the next character. When an error - * occurs, this pointer will be a guess that depends on the particular - * error, but it will always advance at least one byte. - */ -FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) - -> const char* { - constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; - constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; - constexpr const int shiftc[] = {0, 18, 12, 6, 0}; - constexpr const int shifte[] = {0, 6, 4, 2, 0}; - - int len = code_point_length_impl(*s); - // Compute the pointer to the next character early so that the next - // iteration can start working on the next character. Neither Clang - // nor GCC figure out this reordering on their own. - const char* next = s + len + !len; - - using uchar = unsigned char; - - // Assume a four-byte character and load four bytes. Unused bits are - // shifted out. - *c = uint32_t(uchar(s[0]) & masks[len]) << 18; - *c |= uint32_t(uchar(s[1]) & 0x3f) << 12; - *c |= uint32_t(uchar(s[2]) & 0x3f) << 6; - *c |= uint32_t(uchar(s[3]) & 0x3f) << 0; - *c >>= shiftc[len]; - - // Accumulate the various error conditions. - *e = (*c < mins[len]) << 6; // non-canonical encoding - *e |= ((*c >> 11) == 0x1b) << 7; // surrogate half? - *e |= (*c > 0x10FFFF) << 8; // out of range? - *e |= (uchar(s[1]) & 0xc0) >> 2; - *e |= (uchar(s[2]) & 0xc0) >> 4; - *e |= uchar(s[3]) >> 6; - *e ^= 0x2a; // top two bits of each tail byte correct? - *e >>= shifte[len]; - - return next; -} - -constexpr uint32_t invalid_code_point = ~uint32_t(); - -// Invokes f(cp, sv) for every code point cp in s with sv being the string view -// corresponding to the code point. cp is invalid_code_point on error. -template -FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { - auto decode = [f](const char* buf_ptr, const char* ptr) { - auto cp = uint32_t(); - auto error = 0; - auto end = utf8_decode(buf_ptr, &cp, &error); - bool result = f(error ? invalid_code_point : cp, - string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr))); - return result ? (error ? buf_ptr + 1 : end) : nullptr; - }; - auto p = s.data(); - const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. - if (s.size() >= block_size) { - for (auto end = p + s.size() - block_size + 1; p < end;) { - p = decode(p, p); - if (!p) return; - } - } - if (auto num_chars_left = s.data() + s.size() - p) { - char buf[2 * block_size - 1] = {}; - copy_str(p, p + num_chars_left, buf); - const char* buf_ptr = buf; - do { - auto end = decode(buf_ptr, p); - if (!end) return; - p += end - buf_ptr; - buf_ptr = end; - } while (buf_ptr - buf < num_chars_left); - } -} - -template -inline auto compute_width(basic_string_view s) -> size_t { - return s.size(); -} - -// Computes approximate display width of a UTF-8 string. -FMT_CONSTEXPR inline size_t compute_width(string_view s) { - size_t num_code_points = 0; - // It is not a lambda for compatibility with C++14. - struct count_code_points { - size_t* count; - FMT_CONSTEXPR auto operator()(uint32_t cp, string_view) const -> bool { - *count += detail::to_unsigned( - 1 + - (cp >= 0x1100 && - (cp <= 0x115f || // Hangul Jamo init. consonants - cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET - cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET - // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE: - (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) || - (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables - (cp >= 0xf900 && cp <= 0xfaff) || // CJK Compatibility Ideographs - (cp >= 0xfe10 && cp <= 0xfe19) || // Vertical Forms - (cp >= 0xfe30 && cp <= 0xfe6f) || // CJK Compatibility Forms - (cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms - (cp >= 0xffe0 && cp <= 0xffe6) || // Fullwidth Forms - (cp >= 0x20000 && cp <= 0x2fffd) || // CJK - (cp >= 0x30000 && cp <= 0x3fffd) || - // Miscellaneous Symbols and Pictographs + Emoticons: - (cp >= 0x1f300 && cp <= 0x1f64f) || - // Supplemental Symbols and Pictographs: - (cp >= 0x1f900 && cp <= 0x1f9ff)))); - return true; - } - }; - for_each_codepoint(s, count_code_points{&num_code_points}); - return num_code_points; -} - -inline auto compute_width(basic_string_view s) -> size_t { - return compute_width( - string_view(reinterpret_cast(s.data()), s.size())); -} - -template -inline auto code_point_index(basic_string_view s, size_t n) -> size_t { - size_t size = s.size(); - return n < size ? n : size; -} - -// Calculates the index of the nth code point in a UTF-8 string. -inline auto code_point_index(string_view s, size_t n) -> size_t { - const char* data = s.data(); - size_t num_code_points = 0; - for (size_t i = 0, size = s.size(); i != size; ++i) { - if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i; - } - return s.size(); -} - -inline auto code_point_index(basic_string_view s, size_t n) - -> size_t { - return code_point_index( - string_view(reinterpret_cast(s.data()), s.size()), n); -} - -#ifndef FMT_USE_FLOAT128 -# ifdef __SIZEOF_FLOAT128__ -# define FMT_USE_FLOAT128 1 -# else -# define FMT_USE_FLOAT128 0 -# endif -#endif -#if FMT_USE_FLOAT128 -using float128 = __float128; -#else -using float128 = void; -#endif -template using is_float128 = std::is_same; - -template -using is_floating_point = - bool_constant::value || is_float128::value>; - -template ::value> -struct is_fast_float : bool_constant::is_iec559 && - sizeof(T) <= sizeof(double)> {}; -template struct is_fast_float : std::false_type {}; - -template -using is_double_double = bool_constant::digits == 106>; - -#ifndef FMT_USE_FULL_CACHE_DRAGONBOX -# define FMT_USE_FULL_CACHE_DRAGONBOX 0 -#endif - -template -template -void buffer::append(const U* begin, const U* end) { - while (begin != end) { - auto count = to_unsigned(end - begin); - try_reserve(size_ + count); - auto free_cap = capacity_ - size_; - if (free_cap < count) count = free_cap; - std::uninitialized_copy_n(begin, count, make_checked(ptr_ + size_, count)); - size_ += count; - begin += count; - } -} - -template -struct is_locale : std::false_type {}; -template -struct is_locale> : std::true_type {}; -} // namespace detail - -FMT_MODULE_EXPORT_BEGIN - -// The number of characters to store in the basic_memory_buffer object itself -// to avoid dynamic memory allocation. -enum { inline_buffer_size = 500 }; - -/** - \rst - A dynamically growing memory buffer for trivially copyable/constructible types - with the first ``SIZE`` elements stored in the object itself. - - You can use the ``memory_buffer`` type alias for ``char`` instead. - - **Example**:: - - auto out = fmt::memory_buffer(); - format_to(std::back_inserter(out), "The answer is {}.", 42); - - This will append the following output to the ``out`` object: - - .. code-block:: none - - The answer is 42. - - The output can be converted to an ``std::string`` with ``to_string(out)``. - \endrst - */ -template > -class basic_memory_buffer final : public detail::buffer { - private: - T store_[SIZE]; - - // Don't inherit from Allocator avoid generating type_info for it. - Allocator alloc_; - - // Deallocate memory allocated by the buffer. - FMT_CONSTEXPR20 void deallocate() { - T* data = this->data(); - if (data != store_) alloc_.deallocate(data, this->capacity()); - } - - protected: - FMT_CONSTEXPR20 void grow(size_t size) override; - - public: - using value_type = T; - using const_reference = const T&; - - FMT_CONSTEXPR20 explicit basic_memory_buffer( - const Allocator& alloc = Allocator()) - : alloc_(alloc) { - this->set(store_, SIZE); - if (detail::is_constant_evaluated()) detail::fill_n(store_, SIZE, T()); - } - FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); } - - private: - // Move data from other to this buffer. - FMT_CONSTEXPR20 void move(basic_memory_buffer& other) { - alloc_ = std::move(other.alloc_); - T* data = other.data(); - size_t size = other.size(), capacity = other.capacity(); - if (data == other.store_) { - this->set(store_, capacity); - detail::copy_str(other.store_, other.store_ + size, - detail::make_checked(store_, capacity)); - } else { - this->set(data, capacity); - // Set pointer to the inline array so that delete is not called - // when deallocating. - other.set(other.store_, 0); - other.clear(); - } - this->resize(size); - } - - public: - /** - \rst - Constructs a :class:`fmt::basic_memory_buffer` object moving the content - of the other object to it. - \endrst - */ - FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept { - move(other); - } - - /** - \rst - Moves the content of the other ``basic_memory_buffer`` object to this one. - \endrst - */ - auto operator=(basic_memory_buffer&& other) noexcept -> basic_memory_buffer& { - FMT_ASSERT(this != &other, ""); - deallocate(); - move(other); - return *this; - } - - // Returns a copy of the allocator associated with this buffer. - auto get_allocator() const -> Allocator { return alloc_; } - - /** - Resizes the buffer to contain *count* elements. If T is a POD type new - elements may not be initialized. - */ - FMT_CONSTEXPR20 void resize(size_t count) { this->try_resize(count); } - - /** Increases the buffer capacity to *new_capacity*. */ - void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } - - // Directly append data into the buffer - using detail::buffer::append; - template - void append(const ContiguousRange& range) { - append(range.data(), range.data() + range.size()); - } -}; - -template -FMT_CONSTEXPR20 void basic_memory_buffer::grow( - size_t size) { - detail::abort_fuzzing_if(size > 5000); - const size_t max_size = std::allocator_traits::max_size(alloc_); - size_t old_capacity = this->capacity(); - size_t new_capacity = old_capacity + old_capacity / 2; - if (size > new_capacity) - new_capacity = size; - else if (new_capacity > max_size) - new_capacity = size > max_size ? size : max_size; - T* old_data = this->data(); - T* new_data = - std::allocator_traits::allocate(alloc_, new_capacity); - // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy(old_data, old_data + this->size(), - detail::make_checked(new_data, new_capacity)); - this->set(new_data, new_capacity); - // deallocate must not throw according to the standard, but even if it does, - // the buffer already uses the new storage and will deallocate it in - // destructor. - if (old_data != store_) alloc_.deallocate(old_data, old_capacity); -} - -using memory_buffer = basic_memory_buffer; - -template -struct is_contiguous> : std::true_type { -}; - -namespace detail { -#ifdef _WIN32 -FMT_API bool write_console(std::FILE* f, string_view text); -#endif -FMT_API void print(std::FILE*, string_view); -} // namespace detail - -/** A formatting error such as invalid format string. */ -FMT_CLASS_API -class FMT_API format_error : public std::runtime_error { - public: - explicit format_error(const char* message) : std::runtime_error(message) {} - explicit format_error(const std::string& message) - : std::runtime_error(message) {} - format_error(const format_error&) = default; - format_error& operator=(const format_error&) = default; - format_error(format_error&&) = default; - format_error& operator=(format_error&&) = default; - ~format_error() noexcept override FMT_MSC_DEFAULT; -}; - -namespace detail_exported { -#if FMT_USE_NONTYPE_TEMPLATE_ARGS -template struct fixed_string { - constexpr fixed_string(const Char (&str)[N]) { - detail::copy_str(static_cast(str), - str + N, data); - } - Char data[N] = {}; -}; -#endif - -// Converts a compile-time string to basic_string_view. -template -constexpr auto compile_string_to_view(const Char (&s)[N]) - -> basic_string_view { - // Remove trailing NUL character if needed. Won't be present if this is used - // with a raw character array (i.e. not defined as a string). - return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; -} -template -constexpr auto compile_string_to_view(detail::std_string_view s) - -> basic_string_view { - return {s.data(), s.size()}; -} -} // namespace detail_exported - -FMT_BEGIN_DETAIL_NAMESPACE - -template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; -template <> struct is_integral : std::true_type {}; - -template -using is_signed = - std::integral_constant::is_signed || - std::is_same::value>; - -// Returns true if value is negative, false otherwise. -// Same as `value < 0` but doesn't produce warnings if T is an unsigned type. -template ::value)> -constexpr auto is_negative(T value) -> bool { - return value < 0; -} -template ::value)> -constexpr auto is_negative(T) -> bool { - return false; -} - -template -FMT_CONSTEXPR auto is_supported_floating_point(T) -> bool { - if (std::is_same()) return FMT_USE_FLOAT; - if (std::is_same()) return FMT_USE_DOUBLE; - if (std::is_same()) return FMT_USE_LONG_DOUBLE; - return true; -} - -// Smallest of uint32_t, uint64_t, uint128_t that is large enough to -// represent all values of an integral type T. -template -using uint32_or_64_or_128_t = - conditional_t() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS, - uint32_t, - conditional_t() <= 64, uint64_t, uint128_t>>; -template -using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; - -#define FMT_POWERS_OF_10(factor) \ - factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ - (factor)*1000000, (factor)*10000000, (factor)*100000000, \ - (factor)*1000000000 - -// Converts value in the range [0, 100) to a string. -constexpr const char* digits2(size_t value) { - // GCC generates slightly better code when value is pointer-size. - return &"0001020304050607080910111213141516171819" - "2021222324252627282930313233343536373839" - "4041424344454647484950515253545556575859" - "6061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"[value * 2]; -} - -// Sign is a template parameter to workaround a bug in gcc 4.8. -template constexpr Char sign(Sign s) { -#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 604 - static_assert(std::is_same::value, ""); -#endif - return static_cast("\0-+ "[s]); -} - -template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { - int count = 1; - for (;;) { - // Integer division is slow so do it for a group of four digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. - if (n < 10) return count; - if (n < 100) return count + 1; - if (n < 1000) return count + 2; - if (n < 10000) return count + 3; - n /= 10000u; - count += 4; - } -} -#if FMT_USE_INT128 -FMT_CONSTEXPR inline auto count_digits(uint128_opt n) -> int { - return count_digits_fallback(n); -} -#endif - -#ifdef FMT_BUILTIN_CLZLL -// It is a separate function rather than a part of count_digits to workaround -// the lack of static constexpr in constexpr functions. -inline auto do_count_digits(uint64_t n) -> int { - // This has comparable performance to the version by Kendall Willets - // (https://github.com/fmtlib/format-benchmark/blob/master/digits10) - // but uses smaller tables. - // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)). - static constexpr uint8_t bsr2log10[] = { - 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, - 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, - 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, - 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; - auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; - static constexpr const uint64_t zero_or_powers_of_10[] = { - 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), - 10000000000000000000ULL}; - return t - (n < zero_or_powers_of_10[t]); -} -#endif - -// Returns the number of decimal digits in n. Leading zeros are not counted -// except for n == 0 in which case count_digits returns 1. -FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { -#ifdef FMT_BUILTIN_CLZLL - if (!is_constant_evaluated()) { - return do_count_digits(n); - } -#endif - return count_digits_fallback(n); -} - -// Counts the number of digits in n. BITS = log2(radix). -template -FMT_CONSTEXPR auto count_digits(UInt n) -> int { -#ifdef FMT_BUILTIN_CLZ - if (!is_constant_evaluated() && num_bits() == 32) - return (FMT_BUILTIN_CLZ(static_cast(n) | 1) ^ 31) / BITS + 1; -#endif - // Lambda avoids unreachable code warnings from NVHPC. - return [](UInt m) { - int num_digits = 0; - do { - ++num_digits; - } while ((m >>= BITS) != 0); - return num_digits; - }(n); -} - -#ifdef FMT_BUILTIN_CLZ -// It is a separate function rather than a part of count_digits to workaround -// the lack of static constexpr in constexpr functions. -FMT_INLINE auto do_count_digits(uint32_t n) -> int { -// An optimization by Kendall Willets from https://bit.ly/3uOIQrB. -// This increments the upper 32 bits (log10(T) - 1) when >= T is added. -# define FMT_INC(T) (((sizeof(# T) - 1ull) << 32) - T) - static constexpr uint64_t table[] = { - FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8 - FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64 - FMT_INC(100), FMT_INC(100), FMT_INC(100), // 512 - FMT_INC(1000), FMT_INC(1000), FMT_INC(1000), // 4096 - FMT_INC(10000), FMT_INC(10000), FMT_INC(10000), // 32k - FMT_INC(100000), FMT_INC(100000), FMT_INC(100000), // 256k - FMT_INC(1000000), FMT_INC(1000000), FMT_INC(1000000), // 2048k - FMT_INC(10000000), FMT_INC(10000000), FMT_INC(10000000), // 16M - FMT_INC(100000000), FMT_INC(100000000), FMT_INC(100000000), // 128M - FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M - FMT_INC(1000000000), FMT_INC(1000000000) // 4B - }; - auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31]; - return static_cast((n + inc) >> 32); -} -#endif - -// Optional version of count_digits for better performance on 32-bit platforms. -FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { -#ifdef FMT_BUILTIN_CLZ - if (!is_constant_evaluated()) { - return do_count_digits(n); - } -#endif - return count_digits_fallback(n); -} - -template constexpr auto digits10() noexcept -> int { - return std::numeric_limits::digits10; -} -template <> constexpr auto digits10() noexcept -> int { return 38; } -template <> constexpr auto digits10() noexcept -> int { return 38; } - -template struct thousands_sep_result { - std::string grouping; - Char thousands_sep; -}; - -template -FMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result; -template -inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { - auto result = thousands_sep_impl(loc); - return {result.grouping, Char(result.thousands_sep)}; -} -template <> -inline auto thousands_sep(locale_ref loc) -> thousands_sep_result { - return thousands_sep_impl(loc); -} - -template -FMT_API auto decimal_point_impl(locale_ref loc) -> Char; -template inline auto decimal_point(locale_ref loc) -> Char { - return Char(decimal_point_impl(loc)); -} -template <> inline auto decimal_point(locale_ref loc) -> wchar_t { - return decimal_point_impl(loc); -} - -// Compares two characters for equality. -template auto equal2(const Char* lhs, const char* rhs) -> bool { - return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]); -} -inline auto equal2(const char* lhs, const char* rhs) -> bool { - return memcmp(lhs, rhs, 2) == 0; -} - -// Copies two characters from src to dst. -template -FMT_CONSTEXPR20 FMT_INLINE void copy2(Char* dst, const char* src) { - if (!is_constant_evaluated() && sizeof(Char) == sizeof(char)) { - memcpy(dst, src, 2); - return; - } - *dst++ = static_cast(*src++); - *dst = static_cast(*src); -} - -template struct format_decimal_result { - Iterator begin; - Iterator end; -}; - -// Formats a decimal unsigned integer value writing into out pointing to a -// buffer of specified size. The caller must ensure that the buffer is large -// enough. -template -FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size) - -> format_decimal_result { - FMT_ASSERT(size >= count_digits(value), "invalid digit count"); - out += size; - Char* end = out; - while (value >= 100) { - // Integer division is slow so do it for a group of two digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. - out -= 2; - copy2(out, digits2(static_cast(value % 100))); - value /= 100; - } - if (value < 10) { - *--out = static_cast('0' + value); - return {out, end}; - } - out -= 2; - copy2(out, digits2(static_cast(value))); - return {out, end}; -} - -template >::value)> -FMT_CONSTEXPR inline auto format_decimal(Iterator out, UInt value, int size) - -> format_decimal_result { - // Buffer is large enough to hold all digits (digits10 + 1). - Char buffer[digits10() + 1]; - auto end = format_decimal(buffer, value, size).end; - return {out, detail::copy_str_noinline(buffer, end, out)}; -} - -template -FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits, - bool upper = false) -> Char* { - buffer += num_digits; - Char* end = buffer; - do { - const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; - unsigned digit = static_cast(value & ((1 << BASE_BITS) - 1)); - *--buffer = static_cast(BASE_BITS < 4 ? static_cast('0' + digit) - : digits[digit]); - } while ((value >>= BASE_BITS) != 0); - return end; -} - -template -inline auto format_uint(It out, UInt value, int num_digits, bool upper = false) - -> It { - if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { - format_uint(ptr, value, num_digits, upper); - return out; - } - // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). - char buffer[num_bits() / BASE_BITS + 1]; - format_uint(buffer, value, num_digits, upper); - return detail::copy_str_noinline(buffer, buffer + num_digits, out); -} - -// A converter from UTF-8 to UTF-16. -class utf8_to_utf16 { - private: - basic_memory_buffer buffer_; - - public: - FMT_API explicit utf8_to_utf16(string_view s); - operator basic_string_view() const { return {&buffer_[0], size()}; } - auto size() const -> size_t { return buffer_.size() - 1; } - auto c_str() const -> const wchar_t* { return &buffer_[0]; } - auto str() const -> std::wstring { return {&buffer_[0], size()}; } -}; - -namespace dragonbox { - -// Type-specific information that Dragonbox uses. -template struct float_info; - -template <> struct float_info { - using carrier_uint = uint32_t; - static const int exponent_bits = 8; - static const int kappa = 1; - static const int big_divisor = 100; - static const int small_divisor = 10; - static const int min_k = -31; - static const int max_k = 46; - static const int shorter_interval_tie_lower_threshold = -35; - static const int shorter_interval_tie_upper_threshold = -35; -}; - -template <> struct float_info { - using carrier_uint = uint64_t; - static const int exponent_bits = 11; - static const int kappa = 2; - static const int big_divisor = 1000; - static const int small_divisor = 100; - static const int min_k = -292; - static const int max_k = 326; - static const int shorter_interval_tie_lower_threshold = -77; - static const int shorter_interval_tie_upper_threshold = -77; -}; - -// An 80- or 128-bit floating point number. -template -struct float_info::digits == 64 || - std::numeric_limits::digits == 113 || - is_float128::value>> { - using carrier_uint = detail::uint128_t; - static const int exponent_bits = 15; -}; - -// A double-double floating point number. -template -struct float_info::value>> { - using carrier_uint = detail::uint128_t; -}; - -template struct decimal_fp { - using significand_type = typename float_info::carrier_uint; - significand_type significand; - int exponent; -}; - -template FMT_API auto to_decimal(T x) noexcept -> decimal_fp; -} // namespace dragonbox - -// Returns true iff Float has the implicit bit which is not stored. -template constexpr bool has_implicit_bit() { - // An 80-bit FP number has a 64-bit significand an no implicit bit. - return std::numeric_limits::digits != 64; -} - -// Returns the number of significand bits stored in Float. The implicit bit is -// not counted since it is not stored. -template constexpr int num_significand_bits() { - // std::numeric_limits may not support __float128. - return is_float128() ? 112 - : (std::numeric_limits::digits - - (has_implicit_bit() ? 1 : 0)); -} - -template -constexpr auto exponent_mask() -> - typename dragonbox::float_info::carrier_uint { - using fmt_uint = typename dragonbox::float_info::carrier_uint; - return ((fmt_uint(1) << dragonbox::float_info::exponent_bits) - 1) - << num_significand_bits(); -} -template constexpr auto exponent_bias() -> int { - // std::numeric_limits may not support __float128. - return is_float128() ? 16383 - : std::numeric_limits::max_exponent - 1; -} - -// Writes the exponent exp in the form "[+-]d{2,3}" to buffer. -template -FMT_CONSTEXPR auto write_exponent(int exp, It it) -> It { - FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); - if (exp < 0) { - *it++ = static_cast('-'); - exp = -exp; - } else { - *it++ = static_cast('+'); - } - if (exp >= 100) { - const char* top = digits2(to_unsigned(exp / 100)); - if (exp >= 1000) *it++ = static_cast(top[0]); - *it++ = static_cast(top[1]); - exp %= 100; - } - const char* d = digits2(to_unsigned(exp)); - *it++ = static_cast(d[0]); - *it++ = static_cast(d[1]); - return it; -} - -// A floating-point number f * pow(2, e) where F is an unsigned type. -template struct basic_fp { - F f; - int e; - - static constexpr const int num_significand_bits = - static_cast(sizeof(F) * num_bits()); - - constexpr basic_fp() : f(0), e(0) {} - constexpr basic_fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} - - // Constructs fp from an IEEE754 floating-point number. - template FMT_CONSTEXPR basic_fp(Float n) { assign(n); } - - // Assigns n to this and return true iff predecessor is closer than successor. - template ::value)> - FMT_CONSTEXPR auto assign(Float n) -> bool { - static_assert(std::numeric_limits::digits <= 113, "unsupported FP"); - // Assume Float is in the format [sign][exponent][significand]. - using carrier_uint = typename dragonbox::float_info::carrier_uint; - const auto num_float_significand_bits = - detail::num_significand_bits(); - const auto implicit_bit = carrier_uint(1) << num_float_significand_bits; - const auto significand_mask = implicit_bit - 1; - auto u = bit_cast(n); - f = static_cast(u & significand_mask); - auto biased_e = static_cast((u & exponent_mask()) >> - num_float_significand_bits); - // The predecessor is closer if n is a normalized power of 2 (f == 0) - // other than the smallest normalized number (biased_e > 1). - auto is_predecessor_closer = f == 0 && biased_e > 1; - if (biased_e == 0) - biased_e = 1; // Subnormals use biased exponent 1 (min exponent). - else if (has_implicit_bit()) - f += static_cast(implicit_bit); - e = biased_e - exponent_bias() - num_float_significand_bits; - if (!has_implicit_bit()) ++e; - return is_predecessor_closer; - } - - template ::value)> - FMT_CONSTEXPR auto assign(Float n) -> bool { - static_assert(std::numeric_limits::is_iec559, "unsupported FP"); - return assign(static_cast(n)); - } -}; - -using fp = basic_fp; - -// Normalizes the value converted from double and multiplied by (1 << SHIFT). -template -FMT_CONSTEXPR basic_fp normalize(basic_fp value) { - // Handle subnormals. - const auto implicit_bit = F(1) << num_significand_bits(); - const auto shifted_implicit_bit = implicit_bit << SHIFT; - while ((value.f & shifted_implicit_bit) == 0) { - value.f <<= 1; - --value.e; - } - // Subtract 1 to account for hidden bit. - const auto offset = basic_fp::num_significand_bits - - num_significand_bits() - SHIFT - 1; - value.f <<= offset; - value.e -= offset; - return value; -} - -// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. -FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { -#if FMT_USE_INT128 - auto product = static_cast<__uint128_t>(lhs) * rhs; - auto f = static_cast(product >> 64); - return (static_cast(product) & (1ULL << 63)) != 0 ? f + 1 : f; -#else - // Multiply 32-bit parts of significands. - uint64_t mask = (1ULL << 32) - 1; - uint64_t a = lhs >> 32, b = lhs & mask; - uint64_t c = rhs >> 32, d = rhs & mask; - uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; - // Compute mid 64-bit of result and round. - uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); - return ac + (ad >> 32) + (bc >> 32) + (mid >> 32); -#endif -} - -FMT_CONSTEXPR inline fp operator*(fp x, fp y) { - return {multiply(x.f, y.f), x.e + y.e + 64}; -} - -template struct basic_data { - // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. - // These are generated by support/compute-powers.py. - static constexpr uint64_t pow10_significands[87] = { - 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, - 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, - 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, - 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, - 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, - 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, - 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, - 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, - 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, - 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, - 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, - 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, - 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, - 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, - 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, - 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, - 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, - 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, - 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, - 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, - 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, - 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, - 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, - 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, - 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, - 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, - 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, - 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, - 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, - }; - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wnarrowing" -#endif - // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding - // to significands above. - static constexpr int16_t pow10_exponents[87] = { - -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, - -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, - -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, - -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, - -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, - 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, - 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, - 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066}; -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 -# pragma GCC diagnostic pop -#endif - - static constexpr uint64_t power_of_10_64[20] = { - 1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL), - 10000000000000000000ULL}; -}; - -#if FMT_CPLUSPLUS < 201703L -template constexpr uint64_t basic_data::pow10_significands[]; -template constexpr int16_t basic_data::pow10_exponents[]; -template constexpr uint64_t basic_data::power_of_10_64[]; -#endif - -// This is a struct rather than an alias to avoid shadowing warnings in gcc. -struct data : basic_data<> {}; - -// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its -// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. -FMT_CONSTEXPR inline fp get_cached_power(int min_exponent, - int& pow10_exponent) { - const int shift = 32; - // log10(2) = 0x0.4d104d427de7fbcc... - const int64_t significand = 0x4d104d427de7fbcc; - int index = static_cast( - ((min_exponent + fp::num_significand_bits - 1) * (significand >> shift) + - ((int64_t(1) << shift) - 1)) // ceil - >> 32 // arithmetic shift - ); - // Decimal exponent of the first (smallest) cached power of 10. - const int first_dec_exp = -348; - // Difference between 2 consecutive decimal exponents in cached powers of 10. - const int dec_exp_step = 8; - index = (index - first_dec_exp - 1) / dec_exp_step + 1; - pow10_exponent = first_dec_exp + index * dec_exp_step; - // Using *(x + index) instead of x[index] avoids an issue with some compilers - // using the EDG frontend (e.g. nvhpc/22.3 in C++17 mode). - return {*(data::pow10_significands + index), - *(data::pow10_exponents + index)}; -} - -#ifndef _MSC_VER -# define FMT_SNPRINTF snprintf -#else -FMT_API auto fmt_snprintf(char* buf, size_t size, const char* fmt, ...) -> int; -# define FMT_SNPRINTF fmt_snprintf -#endif // _MSC_VER - -// Formats a floating-point number with snprintf using the hexfloat format. -template -auto snprintf_float(T value, int precision, float_specs specs, - buffer& buf) -> int { - // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. - FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer"); - FMT_ASSERT(specs.format == float_format::hex, ""); - static_assert(!std::is_same::value, ""); - - // Build the format string. - char format[7]; // The longest format is "%#.*Le". - char* format_ptr = format; - *format_ptr++ = '%'; - if (specs.showpoint) *format_ptr++ = '#'; - if (precision >= 0) { - *format_ptr++ = '.'; - *format_ptr++ = '*'; - } - if (std::is_same()) *format_ptr++ = 'L'; - *format_ptr++ = specs.upper ? 'A' : 'a'; - *format_ptr = '\0'; - - // Format using snprintf. - auto offset = buf.size(); - for (;;) { - auto begin = buf.data() + offset; - auto capacity = buf.capacity() - offset; - abort_fuzzing_if(precision > 100000); - // Suppress the warning about a nonliteral format string. - // Cannot use auto because of a bug in MinGW (#1532). - int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF; - int result = precision >= 0 - ? snprintf_ptr(begin, capacity, format, precision, value) - : snprintf_ptr(begin, capacity, format, value); - if (result < 0) { - // The buffer will grow exponentially. - buf.try_reserve(buf.capacity() + 1); - continue; - } - auto size = to_unsigned(result); - // Size equal to capacity means that the last character was truncated. - if (size < capacity) { - buf.try_resize(size + offset); - return 0; - } - buf.try_reserve(size + offset + 1); // Add 1 for the terminating '\0'. - } -} - -template -using convert_float_result = - conditional_t::value || sizeof(T) == sizeof(double), - double, T>; - -template -constexpr auto convert_float(T value) -> convert_float_result { - return static_cast>(value); -} - -template -FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, - const fill_t& fill) -> OutputIt { - auto fill_size = fill.size(); - if (fill_size == 1) return detail::fill_n(it, n, fill[0]); - auto data = fill.data(); - for (size_t i = 0; i < n; ++i) - it = copy_str(data, data + fill_size, it); - return it; -} - -// Writes the output of f, padded according to format specifications in specs. -// size: output size in code units. -// width: output display width in (terminal) column positions. -template -FMT_CONSTEXPR auto write_padded(OutputIt out, - const basic_format_specs& specs, - size_t size, size_t width, F&& f) -> OutputIt { - static_assert(align == align::left || align == align::right, ""); - unsigned spec_width = to_unsigned(specs.width); - size_t padding = spec_width > width ? spec_width - width : 0; - // Shifts are encoded as string literals because static constexpr is not - // supported in constexpr functions. - auto* shifts = align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; - size_t left_padding = padding >> shifts[specs.align]; - size_t right_padding = padding - left_padding; - auto it = reserve(out, size + padding * specs.fill.size()); - if (left_padding != 0) it = fill(it, left_padding, specs.fill); - it = f(it); - if (right_padding != 0) it = fill(it, right_padding, specs.fill); - return base_iterator(out, it); -} - -template -constexpr auto write_padded(OutputIt out, const basic_format_specs& specs, - size_t size, F&& f) -> OutputIt { - return write_padded(out, specs, size, size, f); -} - -template -FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, - const basic_format_specs& specs) - -> OutputIt { - return write_padded( - out, specs, bytes.size(), [bytes](reserve_iterator it) { - const char* data = bytes.data(); - return copy_str(data, data + bytes.size(), it); - }); -} - -template -auto write_ptr(OutputIt out, UIntPtr value, - const basic_format_specs* specs) -> OutputIt { - int num_digits = count_digits<4>(value); - auto size = to_unsigned(num_digits) + size_t(2); - auto write = [=](reserve_iterator it) { - *it++ = static_cast('0'); - *it++ = static_cast('x'); - return format_uint<4, Char>(it, value, num_digits); - }; - return specs ? write_padded(out, *specs, size, write) - : base_iterator(out, write(reserve(out, size))); -} - -// Returns true iff the code point cp is printable. -FMT_API auto is_printable(uint32_t cp) -> bool; - -inline auto needs_escape(uint32_t cp) -> bool { - return cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\' || - !is_printable(cp); -} - -template struct find_escape_result { - const Char* begin; - const Char* end; - uint32_t cp; -}; - -template -using make_unsigned_char = - typename conditional_t::value, - std::make_unsigned, - type_identity>::type; - -template -auto find_escape(const Char* begin, const Char* end) - -> find_escape_result { - for (; begin != end; ++begin) { - uint32_t cp = static_cast>(*begin); - if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; - if (needs_escape(cp)) return {begin, begin + 1, cp}; - } - return {begin, nullptr, 0}; -} - -inline auto find_escape(const char* begin, const char* end) - -> find_escape_result { - if (!is_utf8()) return find_escape(begin, end); - auto result = find_escape_result{end, nullptr, 0}; - for_each_codepoint(string_view(begin, to_unsigned(end - begin)), - [&](uint32_t cp, string_view sv) { - if (needs_escape(cp)) { - result = {sv.begin(), sv.end(), cp}; - return false; - } - return true; - }); - return result; -} - -#define FMT_STRING_IMPL(s, base, explicit) \ - [] { \ - /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ - /* Use a macro-like name to avoid shadowing warnings. */ \ - struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \ - using char_type FMT_MAYBE_UNUSED = fmt::remove_cvref_t; \ - FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \ - operator fmt::basic_string_view() const { \ - return fmt::detail_exported::compile_string_to_view(s); \ - } \ - }; \ - return FMT_COMPILE_STRING(); \ - }() - -/** - \rst - Constructs a compile-time format string from a string literal *s*. - - **Example**:: - - // A compile-time error because 'd' is an invalid specifier for strings. - std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); - \endrst - */ -#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string, ) - -template -auto write_codepoint(OutputIt out, char prefix, uint32_t cp) -> OutputIt { - *out++ = static_cast('\\'); - *out++ = static_cast(prefix); - Char buf[width]; - fill_n(buf, width, static_cast('0')); - format_uint<4>(buf, cp, width); - return copy_str(buf, buf + width, out); -} - -template -auto write_escaped_cp(OutputIt out, const find_escape_result& escape) - -> OutputIt { - auto c = static_cast(escape.cp); - switch (escape.cp) { - case '\n': - *out++ = static_cast('\\'); - c = static_cast('n'); - break; - case '\r': - *out++ = static_cast('\\'); - c = static_cast('r'); - break; - case '\t': - *out++ = static_cast('\\'); - c = static_cast('t'); - break; - case '"': - FMT_FALLTHROUGH; - case '\'': - FMT_FALLTHROUGH; - case '\\': - *out++ = static_cast('\\'); - break; - default: - if (is_utf8()) { - if (escape.cp < 0x100) { - return write_codepoint<2, Char>(out, 'x', escape.cp); - } - if (escape.cp < 0x10000) { - return write_codepoint<4, Char>(out, 'u', escape.cp); - } - if (escape.cp < 0x110000) { - return write_codepoint<8, Char>(out, 'U', escape.cp); - } - } - for (Char escape_char : basic_string_view( - escape.begin, to_unsigned(escape.end - escape.begin))) { - out = write_codepoint<2, Char>(out, 'x', - static_cast(escape_char) & 0xFF); - } - return out; - } - *out++ = c; - return out; -} - -template -auto write_escaped_string(OutputIt out, basic_string_view str) - -> OutputIt { - *out++ = static_cast('"'); - auto begin = str.begin(), end = str.end(); - do { - auto escape = find_escape(begin, end); - out = copy_str(begin, escape.begin, out); - begin = escape.end; - if (!begin) break; - out = write_escaped_cp(out, escape); - } while (begin != end); - *out++ = static_cast('"'); - return out; -} - -template -auto write_escaped_char(OutputIt out, Char v) -> OutputIt { - *out++ = static_cast('\''); - if ((needs_escape(static_cast(v)) && v != static_cast('"')) || - v == static_cast('\'')) { - out = write_escaped_cp( - out, find_escape_result{&v, &v + 1, static_cast(v)}); - } else { - *out++ = v; - } - *out++ = static_cast('\''); - return out; -} - -template -FMT_CONSTEXPR auto write_char(OutputIt out, Char value, - const basic_format_specs& specs) - -> OutputIt { - bool is_debug = specs.type == presentation_type::debug; - return write_padded(out, specs, 1, [=](reserve_iterator it) { - if (is_debug) return write_escaped_char(it, value); - *it++ = value; - return it; - }); -} -template -FMT_CONSTEXPR auto write(OutputIt out, Char value, - const basic_format_specs& specs, - locale_ref loc = {}) -> OutputIt { - return check_char_specs(specs) - ? write_char(out, value, specs) - : write(out, static_cast(value), specs, loc); -} - -// Data for write_int that doesn't depend on output iterator type. It is used to -// avoid template code bloat. -template struct write_int_data { - size_t size; - size_t padding; - - FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, - const basic_format_specs& specs) - : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { - if (specs.align == align::numeric) { - auto width = to_unsigned(specs.width); - if (width > size) { - padding = width - size; - size = width; - } - } else if (specs.precision > num_digits) { - size = (prefix >> 24) + to_unsigned(specs.precision); - padding = to_unsigned(specs.precision - num_digits); - } - } -}; - -// Writes an integer in the format -// -// where are written by write_digits(it). -// prefix contains chars in three lower bytes and the size in the fourth byte. -template -FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, - unsigned prefix, - const basic_format_specs& specs, - W write_digits) -> OutputIt { - // Slightly faster check for specs.width == 0 && specs.precision == -1. - if ((specs.width | (specs.precision + 1)) == 0) { - auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); - if (prefix != 0) { - for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) - *it++ = static_cast(p & 0xff); - } - return base_iterator(out, write_digits(it)); - } - auto data = write_int_data(num_digits, prefix, specs); - return write_padded( - out, specs, data.size, [=](reserve_iterator it) { - for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) - *it++ = static_cast(p & 0xff); - it = detail::fill_n(it, data.padding, static_cast('0')); - return write_digits(it); - }); -} - -template class digit_grouping { - private: - thousands_sep_result sep_; - - struct next_state { - std::string::const_iterator group; - int pos; - }; - next_state initial_state() const { return {sep_.grouping.begin(), 0}; } - - // Returns the next digit group separator position. - int next(next_state& state) const { - if (!sep_.thousands_sep) return max_value(); - if (state.group == sep_.grouping.end()) - return state.pos += sep_.grouping.back(); - if (*state.group <= 0 || *state.group == max_value()) - return max_value(); - state.pos += *state.group++; - return state.pos; - } - - public: - explicit digit_grouping(locale_ref loc, bool localized = true) { - if (localized) - sep_ = thousands_sep(loc); - else - sep_.thousands_sep = Char(); - } - explicit digit_grouping(thousands_sep_result sep) : sep_(sep) {} - - Char separator() const { return sep_.thousands_sep; } - - int count_separators(int num_digits) const { - int count = 0; - auto state = initial_state(); - while (num_digits > next(state)) ++count; - return count; - } - - // Applies grouping to digits and write the output to out. - template - Out apply(Out out, basic_string_view digits) const { - auto num_digits = static_cast(digits.size()); - auto separators = basic_memory_buffer(); - separators.push_back(0); - auto state = initial_state(); - while (int i = next(state)) { - if (i >= num_digits) break; - separators.push_back(i); - } - for (int i = 0, sep_index = static_cast(separators.size() - 1); - i < num_digits; ++i) { - if (num_digits - i == separators[sep_index]) { - *out++ = separator(); - --sep_index; - } - *out++ = static_cast(digits[to_unsigned(i)]); - } - return out; - } -}; - -template -auto write_int_localized(OutputIt out, UInt value, unsigned prefix, - const basic_format_specs& specs, - const digit_grouping& grouping) -> OutputIt { - static_assert(std::is_same, UInt>::value, ""); - int num_digits = count_digits(value); - char digits[40]; - format_decimal(digits, value, num_digits); - unsigned size = to_unsigned((prefix != 0 ? 1 : 0) + num_digits + - grouping.count_separators(num_digits)); - return write_padded( - out, specs, size, size, [&](reserve_iterator it) { - if (prefix != 0) { - char sign = static_cast(prefix); - *it++ = static_cast(sign); - } - return grouping.apply(it, string_view(digits, to_unsigned(num_digits))); - }); -} - -template -auto write_int_localized(OutputIt& out, UInt value, unsigned prefix, - const basic_format_specs& specs, locale_ref loc) - -> bool { - auto grouping = digit_grouping(loc); - out = write_int_localized(out, value, prefix, specs, grouping); - return true; -} - -FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { - prefix |= prefix != 0 ? value << 8 : value; - prefix += (1u + (value > 0xff ? 1 : 0)) << 24; -} - -template struct write_int_arg { - UInt abs_value; - unsigned prefix; -}; - -template -FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) - -> write_int_arg> { - auto prefix = 0u; - auto abs_value = static_cast>(value); - if (is_negative(value)) { - prefix = 0x01000000 | '-'; - abs_value = 0 - abs_value; - } else { - constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', - 0x1000000u | ' '}; - prefix = prefixes[sign]; - } - return {abs_value, prefix}; -} - -template -FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, - const basic_format_specs& specs, - locale_ref loc) -> OutputIt { - static_assert(std::is_same>::value, ""); - auto abs_value = arg.abs_value; - auto prefix = arg.prefix; - switch (specs.type) { - case presentation_type::none: - case presentation_type::dec: { - if (specs.localized && - write_int_localized(out, static_cast>(abs_value), - prefix, specs, loc)) { - return out; - } - auto num_digits = count_digits(abs_value); - return write_int( - out, num_digits, prefix, specs, [=](reserve_iterator it) { - return format_decimal(it, abs_value, num_digits).end; - }); - } - case presentation_type::hex_lower: - case presentation_type::hex_upper: { - bool upper = specs.type == presentation_type::hex_upper; - if (specs.alt) - prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0'); - int num_digits = count_digits<4>(abs_value); - return write_int( - out, num_digits, prefix, specs, [=](reserve_iterator it) { - return format_uint<4, Char>(it, abs_value, num_digits, upper); - }); - } - case presentation_type::bin_lower: - case presentation_type::bin_upper: { - bool upper = specs.type == presentation_type::bin_upper; - if (specs.alt) - prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0'); - int num_digits = count_digits<1>(abs_value); - return write_int(out, num_digits, prefix, specs, - [=](reserve_iterator it) { - return format_uint<1, Char>(it, abs_value, num_digits); - }); - } - case presentation_type::oct: { - int num_digits = count_digits<3>(abs_value); - // Octal prefix '0' is counted as a digit, so only add it if precision - // is not greater than the number of digits. - if (specs.alt && specs.precision <= num_digits && abs_value != 0) - prefix_append(prefix, '0'); - return write_int(out, num_digits, prefix, specs, - [=](reserve_iterator it) { - return format_uint<3, Char>(it, abs_value, num_digits); - }); - } - case presentation_type::chr: - return write_char(out, static_cast(abs_value), specs); - default: - throw_format_error("invalid type specifier"); - } - return out; -} -template -FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline( - OutputIt out, write_int_arg arg, const basic_format_specs& specs, - locale_ref loc) -> OutputIt { - return write_int(out, arg, specs, loc); -} -template ::value && - !std::is_same::value && - std::is_same>::value)> -FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const basic_format_specs& specs, - locale_ref loc) -> OutputIt { - return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs, - loc); -} -// An inlined version of write used in format string compilation. -template ::value && - !std::is_same::value && - !std::is_same>::value)> -FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const basic_format_specs& specs, - locale_ref loc) -> OutputIt { - return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); -} - -// An output iterator that counts the number of objects written to it and -// discards them. -class counting_iterator { - private: - size_t count_; - - public: - using iterator_category = std::output_iterator_tag; - using difference_type = std::ptrdiff_t; - using pointer = void; - using reference = void; - FMT_UNCHECKED_ITERATOR(counting_iterator); - - struct value_type { - template FMT_CONSTEXPR void operator=(const T&) {} - }; - - FMT_CONSTEXPR counting_iterator() : count_(0) {} - - FMT_CONSTEXPR size_t count() const { return count_; } - - FMT_CONSTEXPR counting_iterator& operator++() { - ++count_; - return *this; - } - FMT_CONSTEXPR counting_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - FMT_CONSTEXPR friend counting_iterator operator+(counting_iterator it, - difference_type n) { - it.count_ += static_cast(n); - return it; - } - - FMT_CONSTEXPR value_type operator*() const { return {}; } -}; - -template -FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, - const basic_format_specs& specs) -> OutputIt { - auto data = s.data(); - auto size = s.size(); - if (specs.precision >= 0 && to_unsigned(specs.precision) < size) - size = code_point_index(s, to_unsigned(specs.precision)); - bool is_debug = specs.type == presentation_type::debug; - size_t width = 0; - if (specs.width != 0) { - if (is_debug) - width = write_escaped_string(counting_iterator{}, s).count(); - else - width = compute_width(basic_string_view(data, size)); - } - return write_padded(out, specs, size, width, - [=](reserve_iterator it) { - if (is_debug) return write_escaped_string(it, s); - return copy_str(data, data + size, it); - }); -} -template -FMT_CONSTEXPR auto write(OutputIt out, - basic_string_view> s, - const basic_format_specs& specs, locale_ref) - -> OutputIt { - check_string_type_spec(specs.type); - return write(out, s, specs); -} -template -FMT_CONSTEXPR auto write(OutputIt out, const Char* s, - const basic_format_specs& specs, locale_ref) - -> OutputIt { - return check_cstring_type_spec(specs.type) - ? write(out, basic_string_view(s), specs, {}) - : write_ptr(out, bit_cast(s), &specs); -} - -template ::value && - !std::is_same::value && - !std::is_same::value)> -FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { - auto abs_value = static_cast>(value); - bool negative = is_negative(value); - // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer. - if (negative) abs_value = ~abs_value + 1; - int num_digits = count_digits(abs_value); - auto size = (negative ? 1 : 0) + static_cast(num_digits); - auto it = reserve(out, size); - if (auto ptr = to_pointer(it, size)) { - if (negative) *ptr++ = static_cast('-'); - format_decimal(ptr, abs_value, num_digits); - return out; - } - if (negative) *it++ = static_cast('-'); - it = format_decimal(it, abs_value, num_digits).end; - return base_iterator(out, it); -} - -template -FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, - basic_format_specs specs, - const float_specs& fspecs) -> OutputIt { - auto str = - isnan ? (fspecs.upper ? "NAN" : "nan") : (fspecs.upper ? "INF" : "inf"); - constexpr size_t str_size = 3; - auto sign = fspecs.sign; - auto size = str_size + (sign ? 1 : 0); - // Replace '0'-padding with space for non-finite values. - const bool is_zero_fill = - specs.fill.size() == 1 && *specs.fill.data() == static_cast('0'); - if (is_zero_fill) specs.fill[0] = static_cast(' '); - return write_padded(out, specs, size, [=](reserve_iterator it) { - if (sign) *it++ = detail::sign(sign); - return copy_str(str, str + str_size, it); - }); -} - -// A decimal floating-point number significand * pow(10, exp). -struct big_decimal_fp { - const char* significand; - int significand_size; - int exponent; -}; - -constexpr auto get_significand_size(const big_decimal_fp& f) -> int { - return f.significand_size; -} -template -inline auto get_significand_size(const dragonbox::decimal_fp& f) -> int { - return count_digits(f.significand); -} - -template -constexpr auto write_significand(OutputIt out, const char* significand, - int significand_size) -> OutputIt { - return copy_str(significand, significand + significand_size, out); -} -template -inline auto write_significand(OutputIt out, UInt significand, - int significand_size) -> OutputIt { - return format_decimal(out, significand, significand_size).end; -} -template -FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, - int significand_size, int exponent, - const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { - out = write_significand(out, significand, significand_size); - return detail::fill_n(out, exponent, static_cast('0')); - } - auto buffer = memory_buffer(); - write_significand(appender(buffer), significand, significand_size); - detail::fill_n(appender(buffer), exponent, '0'); - return grouping.apply(out, string_view(buffer.data(), buffer.size())); -} - -template ::value)> -inline auto write_significand(Char* out, UInt significand, int significand_size, - int integral_size, Char decimal_point) -> Char* { - if (!decimal_point) - return format_decimal(out, significand, significand_size).end; - out += significand_size + 1; - Char* end = out; - int floating_size = significand_size - integral_size; - for (int i = floating_size / 2; i > 0; --i) { - out -= 2; - copy2(out, digits2(static_cast(significand % 100))); - significand /= 100; - } - if (floating_size % 2 != 0) { - *--out = static_cast('0' + significand % 10); - significand /= 10; - } - *--out = decimal_point; - format_decimal(out - integral_size, significand, integral_size); - return end; -} - -template >::value)> -inline auto write_significand(OutputIt out, UInt significand, - int significand_size, int integral_size, - Char decimal_point) -> OutputIt { - // Buffer is large enough to hold digits (digits10 + 1) and a decimal point. - Char buffer[digits10() + 2]; - auto end = write_significand(buffer, significand, significand_size, - integral_size, decimal_point); - return detail::copy_str_noinline(buffer, end, out); -} - -template -FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, - int significand_size, int integral_size, - Char decimal_point) -> OutputIt { - out = detail::copy_str_noinline(significand, - significand + integral_size, out); - if (!decimal_point) return out; - *out++ = decimal_point; - return detail::copy_str_noinline(significand + integral_size, - significand + significand_size, out); -} - -template -FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, - int significand_size, int integral_size, - Char decimal_point, - const Grouping& grouping) -> OutputIt { - if (!grouping.separator()) { - return write_significand(out, significand, significand_size, integral_size, - decimal_point); - } - auto buffer = basic_memory_buffer(); - write_significand(buffer_appender(buffer), significand, - significand_size, integral_size, decimal_point); - grouping.apply( - out, basic_string_view(buffer.data(), to_unsigned(integral_size))); - return detail::copy_str_noinline(buffer.data() + integral_size, - buffer.end(), out); -} - -template > -FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, - const basic_format_specs& specs, - float_specs fspecs, locale_ref loc) - -> OutputIt { - auto significand = f.significand; - int significand_size = get_significand_size(f); - const Char zero = static_cast('0'); - auto sign = fspecs.sign; - size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); - using iterator = reserve_iterator; - - Char decimal_point = - fspecs.locale ? detail::decimal_point(loc) : static_cast('.'); - - int output_exp = f.exponent + significand_size - 1; - auto use_exp_format = [=]() { - if (fspecs.format == float_format::exp) return true; - if (fspecs.format != float_format::general) return false; - // Use the fixed notation if the exponent is in [exp_lower, exp_upper), - // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. - const int exp_lower = -4, exp_upper = 16; - return output_exp < exp_lower || - output_exp >= (fspecs.precision > 0 ? fspecs.precision : exp_upper); - }; - if (use_exp_format()) { - int num_zeros = 0; - if (fspecs.showpoint) { - num_zeros = fspecs.precision - significand_size; - if (num_zeros < 0) num_zeros = 0; - size += to_unsigned(num_zeros); - } else if (significand_size == 1) { - decimal_point = Char(); - } - auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp; - int exp_digits = 2; - if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; - - size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); - char exp_char = fspecs.upper ? 'E' : 'e'; - auto write = [=](iterator it) { - if (sign) *it++ = detail::sign(sign); - // Insert a decimal point after the first digit and add an exponent. - it = write_significand(it, significand, significand_size, 1, - decimal_point); - if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero); - *it++ = static_cast(exp_char); - return write_exponent(output_exp, it); - }; - return specs.width > 0 ? write_padded(out, specs, size, write) - : base_iterator(out, write(reserve(out, size))); - } - - int exp = f.exponent + significand_size; - if (f.exponent >= 0) { - // 1234e5 -> 123400000[.0+] - size += to_unsigned(f.exponent); - int num_zeros = fspecs.precision - exp; - abort_fuzzing_if(num_zeros > 5000); - if (fspecs.showpoint) { - ++size; - if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1; - if (num_zeros > 0) size += to_unsigned(num_zeros); - } - auto grouping = Grouping(loc, fspecs.locale); - size += to_unsigned(grouping.count_separators(exp)); - return write_padded(out, specs, size, [&](iterator it) { - if (sign) *it++ = detail::sign(sign); - it = write_significand(it, significand, significand_size, - f.exponent, grouping); - if (!fspecs.showpoint) return it; - *it++ = decimal_point; - return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; - }); - } else if (exp > 0) { - // 1234e-2 -> 12.34[0+] - int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; - size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); - auto grouping = Grouping(loc, fspecs.locale); - size += to_unsigned(grouping.count_separators(significand_size)); - return write_padded(out, specs, size, [&](iterator it) { - if (sign) *it++ = detail::sign(sign); - it = write_significand(it, significand, significand_size, exp, - decimal_point, grouping); - return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; - }); - } - // 1234e-6 -> 0.001234 - int num_zeros = -exp; - if (significand_size == 0 && fspecs.precision >= 0 && - fspecs.precision < num_zeros) { - num_zeros = fspecs.precision; - } - bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint; - size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); - return write_padded(out, specs, size, [&](iterator it) { - if (sign) *it++ = detail::sign(sign); - *it++ = zero; - if (!pointy) return it; - *it++ = decimal_point; - it = detail::fill_n(it, num_zeros, zero); - return write_significand(it, significand, significand_size); - }); -} - -template class fallback_digit_grouping { - public: - constexpr fallback_digit_grouping(locale_ref, bool) {} - - constexpr Char separator() const { return Char(); } - - constexpr int count_separators(int) const { return 0; } - - template - constexpr Out apply(Out out, basic_string_view) const { - return out; - } -}; - -template -FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, - const basic_format_specs& specs, - float_specs fspecs, locale_ref loc) - -> OutputIt { - if (is_constant_evaluated()) { - return do_write_float>(out, f, specs, fspecs, - loc); - } else { - return do_write_float(out, f, specs, fspecs, loc); - } -} - -template constexpr bool isnan(T value) { - return !(value >= value); // std::isnan doesn't support __float128. -} - -template -struct has_isfinite : std::false_type {}; - -template -struct has_isfinite> - : std::true_type {}; - -template ::value&& - has_isfinite::value)> -FMT_CONSTEXPR20 bool isfinite(T value) { - constexpr T inf = T(std::numeric_limits::infinity()); - if (is_constant_evaluated()) - return !detail::isnan(value) && value != inf && value != -inf; - return std::isfinite(value); -} -template ::value)> -FMT_CONSTEXPR bool isfinite(T value) { - T inf = T(std::numeric_limits::infinity()); - // std::isfinite doesn't support __float128. - return !detail::isnan(value) && value != inf && value != -inf; -} - -template ::value)> -FMT_INLINE FMT_CONSTEXPR bool signbit(T value) { - if (is_constant_evaluated()) { -#ifdef __cpp_if_constexpr - if constexpr (std::numeric_limits::is_iec559) { - auto bits = detail::bit_cast(static_cast(value)); - return (bits >> (num_bits() - 1)) != 0; - } -#endif - } - return std::signbit(static_cast(value)); -} - -enum class round_direction { unknown, up, down }; - -// Given the divisor (normally a power of 10), the remainder = v % divisor for -// some number v and the error, returns whether v should be rounded up, down, or -// whether the rounding direction can't be determined due to error. -// error should be less than divisor / 2. -FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor, - uint64_t remainder, - uint64_t error) { - FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow. - FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow. - FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow. - // Round down if (remainder + error) * 2 <= divisor. - if (remainder <= divisor - remainder && error * 2 <= divisor - remainder * 2) - return round_direction::down; - // Round up if (remainder - error) * 2 >= divisor. - if (remainder >= error && - remainder - error >= divisor - (remainder - error)) { - return round_direction::up; - } - return round_direction::unknown; -} - -namespace digits { -enum result { - more, // Generate more digits. - done, // Done generating digits. - error // Digit generation cancelled due to an error. -}; -} - -struct gen_digits_handler { - char* buf; - int size; - int precision; - int exp10; - bool fixed; - - FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor, - uint64_t remainder, uint64_t error, - bool integral) { - FMT_ASSERT(remainder < divisor, ""); - buf[size++] = digit; - if (!integral && error >= remainder) return digits::error; - if (size < precision) return digits::more; - if (!integral) { - // Check if error * 2 < divisor with overflow prevention. - // The check is not needed for the integral part because error = 1 - // and divisor > (1 << 32) there. - if (error >= divisor || error >= divisor - error) return digits::error; - } else { - FMT_ASSERT(error == 1 && divisor > 2, ""); - } - auto dir = get_round_direction(divisor, remainder, error); - if (dir != round_direction::up) - return dir == round_direction::down ? digits::done : digits::error; - ++buf[size - 1]; - for (int i = size - 1; i > 0 && buf[i] > '9'; --i) { - buf[i] = '0'; - ++buf[i - 1]; - } - if (buf[0] > '9') { - buf[0] = '1'; - if (fixed) - buf[size++] = '0'; - else - ++exp10; - } - return digits::done; - } -}; - -inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { - // Adjust fixed precision by exponent because it is relative to decimal - // point. - if (exp10 > 0 && precision > max_value() - exp10) - FMT_THROW(format_error("number is too big")); - precision += exp10; -} - -// Generates output using the Grisu digit-gen algorithm. -// error: the size of the region (lower, upper) outside of which numbers -// definitely do not round to value (Delta in Grisu3). -FMT_INLINE FMT_CONSTEXPR20 auto grisu_gen_digits(fp value, uint64_t error, - int& exp, - gen_digits_handler& handler) - -> digits::result { - const fp one(1ULL << -value.e, value.e); - // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be - // zero because it contains a product of two 64-bit numbers with MSB set (due - // to normalization) - 1, shifted right by at most 60 bits. - auto integral = static_cast(value.f >> -one.e); - FMT_ASSERT(integral != 0, ""); - FMT_ASSERT(integral == value.f >> -one.e, ""); - // The fractional part of scaled value (p2 in Grisu) c = value % one. - uint64_t fractional = value.f & (one.f - 1); - exp = count_digits(integral); // kappa in Grisu. - // Non-fixed formats require at least one digit and no precision adjustment. - if (handler.fixed) { - adjust_precision(handler.precision, exp + handler.exp10); - // Check if precision is satisfied just by leading zeros, e.g. - // format("{:.2f}", 0.001) gives "0.00" without generating any digits. - if (handler.precision <= 0) { - if (handler.precision < 0) return digits::done; - // Divide by 10 to prevent overflow. - uint64_t divisor = data::power_of_10_64[exp - 1] << -one.e; - auto dir = get_round_direction(divisor, value.f / 10, error * 10); - if (dir == round_direction::unknown) return digits::error; - handler.buf[handler.size++] = dir == round_direction::up ? '1' : '0'; - return digits::done; - } - } - // Generate digits for the integral part. This can produce up to 10 digits. - do { - uint32_t digit = 0; - auto divmod_integral = [&](uint32_t divisor) { - digit = integral / divisor; - integral %= divisor; - }; - // This optimization by Milo Yip reduces the number of integer divisions by - // one per iteration. - switch (exp) { - case 10: - divmod_integral(1000000000); - break; - case 9: - divmod_integral(100000000); - break; - case 8: - divmod_integral(10000000); - break; - case 7: - divmod_integral(1000000); - break; - case 6: - divmod_integral(100000); - break; - case 5: - divmod_integral(10000); - break; - case 4: - divmod_integral(1000); - break; - case 3: - divmod_integral(100); - break; - case 2: - divmod_integral(10); - break; - case 1: - digit = integral; - integral = 0; - break; - default: - FMT_ASSERT(false, "invalid number of digits"); - } - --exp; - auto remainder = (static_cast(integral) << -one.e) + fractional; - auto result = handler.on_digit(static_cast('0' + digit), - data::power_of_10_64[exp] << -one.e, - remainder, error, true); - if (result != digits::more) return result; - } while (exp > 0); - // Generate digits for the fractional part. - for (;;) { - fractional *= 10; - error *= 10; - char digit = static_cast('0' + (fractional >> -one.e)); - fractional &= one.f - 1; - --exp; - auto result = handler.on_digit(digit, one.f, fractional, error, false); - if (result != digits::more) return result; - } -} - -class bigint { - private: - // A bigint is stored as an array of bigits (big digits), with bigit at index - // 0 being the least significant one. - using bigit = uint32_t; - using double_bigit = uint64_t; - enum { bigits_capacity = 32 }; - basic_memory_buffer bigits_; - int exp_; - - FMT_CONSTEXPR20 bigit operator[](int index) const { - return bigits_[to_unsigned(index)]; - } - FMT_CONSTEXPR20 bigit& operator[](int index) { - return bigits_[to_unsigned(index)]; - } - - static constexpr const int bigit_bits = num_bits(); - - friend struct formatter; - - FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) { - auto result = static_cast((*this)[index]) - other - borrow; - (*this)[index] = static_cast(result); - borrow = static_cast(result >> (bigit_bits * 2 - 1)); - } - - FMT_CONSTEXPR20 void remove_leading_zeros() { - int num_bigits = static_cast(bigits_.size()) - 1; - while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; - bigits_.resize(to_unsigned(num_bigits + 1)); - } - - // Computes *this -= other assuming aligned bigints and *this >= other. - FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) { - FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); - FMT_ASSERT(compare(*this, other) >= 0, ""); - bigit borrow = 0; - int i = other.exp_ - exp_; - for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) - subtract_bigits(i, other.bigits_[j], borrow); - while (borrow > 0) subtract_bigits(i, 0, borrow); - remove_leading_zeros(); - } - - FMT_CONSTEXPR20 void multiply(uint32_t value) { - const double_bigit wide_value = value; - bigit carry = 0; - for (size_t i = 0, n = bigits_.size(); i < n; ++i) { - double_bigit result = bigits_[i] * wide_value + carry; - bigits_[i] = static_cast(result); - carry = static_cast(result >> bigit_bits); - } - if (carry != 0) bigits_.push_back(carry); - } - - template ::value || - std::is_same::value)> - FMT_CONSTEXPR20 void multiply(UInt value) { - using half_uint = - conditional_t::value, uint64_t, uint32_t>; - const int shift = num_bits() - bigit_bits; - const UInt lower = static_cast(value); - const UInt upper = value >> num_bits(); - UInt carry = 0; - for (size_t i = 0, n = bigits_.size(); i < n; ++i) { - UInt result = lower * bigits_[i] + static_cast(carry); - carry = (upper * bigits_[i] << shift) + (result >> bigit_bits) + - (carry >> bigit_bits); - bigits_[i] = static_cast(result); - } - while (carry != 0) { - bigits_.push_back(static_cast(carry)); - carry >>= bigit_bits; - } - } - - template ::value || - std::is_same::value)> - FMT_CONSTEXPR20 void assign(UInt n) { - size_t num_bigits = 0; - do { - bigits_[num_bigits++] = static_cast(n); - n >>= bigit_bits; - } while (n != 0); - bigits_.resize(num_bigits); - exp_ = 0; - } - - public: - FMT_CONSTEXPR20 bigint() : exp_(0) {} - explicit bigint(uint64_t n) { assign(n); } - - bigint(const bigint&) = delete; - void operator=(const bigint&) = delete; - - FMT_CONSTEXPR20 void assign(const bigint& other) { - auto size = other.bigits_.size(); - bigits_.resize(size); - auto data = other.bigits_.data(); - std::copy(data, data + size, make_checked(bigits_.data(), size)); - exp_ = other.exp_; - } - - template FMT_CONSTEXPR20 void operator=(Int n) { - FMT_ASSERT(n > 0, ""); - assign(uint64_or_128_t(n)); - } - - FMT_CONSTEXPR20 int num_bigits() const { - return static_cast(bigits_.size()) + exp_; - } - - FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) { - FMT_ASSERT(shift >= 0, ""); - exp_ += shift / bigit_bits; - shift %= bigit_bits; - if (shift == 0) return *this; - bigit carry = 0; - for (size_t i = 0, n = bigits_.size(); i < n; ++i) { - bigit c = bigits_[i] >> (bigit_bits - shift); - bigits_[i] = (bigits_[i] << shift) + carry; - carry = c; - } - if (carry != 0) bigits_.push_back(carry); - return *this; - } - - template FMT_CONSTEXPR20 bigint& operator*=(Int value) { - FMT_ASSERT(value > 0, ""); - multiply(uint32_or_64_or_128_t(value)); - return *this; - } - - friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) { - int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); - if (num_lhs_bigits != num_rhs_bigits) - return num_lhs_bigits > num_rhs_bigits ? 1 : -1; - int i = static_cast(lhs.bigits_.size()) - 1; - int j = static_cast(rhs.bigits_.size()) - 1; - int end = i - j; - if (end < 0) end = 0; - for (; i >= end; --i, --j) { - bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j]; - if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1; - } - if (i != j) return i > j ? 1 : -1; - return 0; - } - - // Returns compare(lhs1 + lhs2, rhs). - friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2, - const bigint& rhs) { - auto minimum = [](int a, int b) { return a < b ? a : b; }; - auto maximum = [](int a, int b) { return a > b ? a : b; }; - int max_lhs_bigits = maximum(lhs1.num_bigits(), lhs2.num_bigits()); - int num_rhs_bigits = rhs.num_bigits(); - if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; - if (max_lhs_bigits > num_rhs_bigits) return 1; - auto get_bigit = [](const bigint& n, int i) -> bigit { - return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0; - }; - double_bigit borrow = 0; - int min_exp = minimum(minimum(lhs1.exp_, lhs2.exp_), rhs.exp_); - for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { - double_bigit sum = - static_cast(get_bigit(lhs1, i)) + get_bigit(lhs2, i); - bigit rhs_bigit = get_bigit(rhs, i); - if (sum > rhs_bigit + borrow) return 1; - borrow = rhs_bigit + borrow - sum; - if (borrow > 1) return -1; - borrow <<= bigit_bits; - } - return borrow != 0 ? -1 : 0; - } - - // Assigns pow(10, exp) to this bigint. - FMT_CONSTEXPR20 void assign_pow10(int exp) { - FMT_ASSERT(exp >= 0, ""); - if (exp == 0) return *this = 1; - // Find the top bit. - int bitmask = 1; - while (exp >= bitmask) bitmask <<= 1; - bitmask >>= 1; - // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by - // repeated squaring and multiplication. - *this = 5; - bitmask >>= 1; - while (bitmask != 0) { - square(); - if ((exp & bitmask) != 0) *this *= 5; - bitmask >>= 1; - } - *this <<= exp; // Multiply by pow(2, exp) by shifting. - } - - FMT_CONSTEXPR20 void square() { - int num_bigits = static_cast(bigits_.size()); - int num_result_bigits = 2 * num_bigits; - basic_memory_buffer n(std::move(bigits_)); - bigits_.resize(to_unsigned(num_result_bigits)); - auto sum = uint128_t(); - for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { - // Compute bigit at position bigit_index of the result by adding - // cross-product terms n[i] * n[j] such that i + j == bigit_index. - for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { - // Most terms are multiplied twice which can be optimized in the future. - sum += static_cast(n[i]) * n[j]; - } - (*this)[bigit_index] = static_cast(sum); - sum >>= num_bits(); // Compute the carry. - } - // Do the same for the top half. - for (int bigit_index = num_bigits; bigit_index < num_result_bigits; - ++bigit_index) { - for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) - sum += static_cast(n[i++]) * n[j--]; - (*this)[bigit_index] = static_cast(sum); - sum >>= num_bits(); - } - remove_leading_zeros(); - exp_ *= 2; - } - - // If this bigint has a bigger exponent than other, adds trailing zero to make - // exponents equal. This simplifies some operations such as subtraction. - FMT_CONSTEXPR20 void align(const bigint& other) { - int exp_difference = exp_ - other.exp_; - if (exp_difference <= 0) return; - int num_bigits = static_cast(bigits_.size()); - bigits_.resize(to_unsigned(num_bigits + exp_difference)); - for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) - bigits_[j] = bigits_[i]; - std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); - exp_ -= exp_difference; - } - - // Divides this bignum by divisor, assigning the remainder to this and - // returning the quotient. - FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) { - FMT_ASSERT(this != &divisor, ""); - if (compare(*this, divisor) < 0) return 0; - FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); - align(divisor); - int quotient = 0; - do { - subtract_aligned(divisor); - ++quotient; - } while (compare(*this, divisor) >= 0); - return quotient; - } -}; - -// format_dragon flags. -enum dragon { - predecessor_closer = 1, - fixup = 2, // Run fixup to correct exp10 which can be off by one. - fixed = 4, -}; - -// Formats a floating-point number using a variation of the Fixed-Precision -// Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White: -// https://fmt.dev/papers/p372-steele.pdf. -FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, - unsigned flags, int num_digits, - buffer& buf, int& exp10) { - bigint numerator; // 2 * R in (FPP)^2. - bigint denominator; // 2 * S in (FPP)^2. - // lower and upper are differences between value and corresponding boundaries. - bigint lower; // (M^- in (FPP)^2). - bigint upper_store; // upper's value if different from lower. - bigint* upper = nullptr; // (M^+ in (FPP)^2). - // Shift numerator and denominator by an extra bit or two (if lower boundary - // is closer) to make lower and upper integers. This eliminates multiplication - // by 2 during later computations. - bool is_predecessor_closer = (flags & dragon::predecessor_closer) != 0; - int shift = is_predecessor_closer ? 2 : 1; - if (value.e >= 0) { - numerator = value.f; - numerator <<= value.e + shift; - lower = 1; - lower <<= value.e; - if (is_predecessor_closer) { - upper_store = 1; - upper_store <<= value.e + 1; - upper = &upper_store; - } - denominator.assign_pow10(exp10); - denominator <<= shift; - } else if (exp10 < 0) { - numerator.assign_pow10(-exp10); - lower.assign(numerator); - if (is_predecessor_closer) { - upper_store.assign(numerator); - upper_store <<= 1; - upper = &upper_store; - } - numerator *= value.f; - numerator <<= shift; - denominator = 1; - denominator <<= shift - value.e; - } else { - numerator = value.f; - numerator <<= shift; - denominator.assign_pow10(exp10); - denominator <<= shift - value.e; - lower = 1; - if (is_predecessor_closer) { - upper_store = 1ULL << 1; - upper = &upper_store; - } - } - int even = static_cast((value.f & 1) == 0); - if (!upper) upper = &lower; - if ((flags & dragon::fixup) != 0) { - if (add_compare(numerator, *upper, denominator) + even <= 0) { - --exp10; - numerator *= 10; - if (num_digits < 0) { - lower *= 10; - if (upper != &lower) *upper *= 10; - } - } - if ((flags & dragon::fixed) != 0) adjust_precision(num_digits, exp10 + 1); - } - // Invariant: value == (numerator / denominator) * pow(10, exp10). - if (num_digits < 0) { - // Generate the shortest representation. - num_digits = 0; - char* data = buf.data(); - for (;;) { - int digit = numerator.divmod_assign(denominator); - bool low = compare(numerator, lower) - even < 0; // numerator <[=] lower. - // numerator + upper >[=] pow10: - bool high = add_compare(numerator, *upper, denominator) + even > 0; - data[num_digits++] = static_cast('0' + digit); - if (low || high) { - if (!low) { - ++data[num_digits - 1]; - } else if (high) { - int result = add_compare(numerator, numerator, denominator); - // Round half to even. - if (result > 0 || (result == 0 && (digit % 2) != 0)) - ++data[num_digits - 1]; - } - buf.try_resize(to_unsigned(num_digits)); - exp10 -= num_digits - 1; - return; - } - numerator *= 10; - lower *= 10; - if (upper != &lower) *upper *= 10; - } - } - // Generate the given number of digits. - exp10 -= num_digits - 1; - if (num_digits == 0) { - denominator *= 10; - auto digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; - buf.push_back(digit); - return; - } - buf.try_resize(to_unsigned(num_digits)); - for (int i = 0; i < num_digits - 1; ++i) { - int digit = numerator.divmod_assign(denominator); - buf[i] = static_cast('0' + digit); - numerator *= 10; - } - int digit = numerator.divmod_assign(denominator); - auto result = add_compare(numerator, numerator, denominator); - if (result > 0 || (result == 0 && (digit % 2) != 0)) { - if (digit == 9) { - const auto overflow = '0' + 10; - buf[num_digits - 1] = overflow; - // Propagate the carry. - for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) { - buf[i] = '0'; - ++buf[i - 1]; - } - if (buf[0] == overflow) { - buf[0] = '1'; - ++exp10; - } - return; - } - ++digit; - } - buf[num_digits - 1] = static_cast('0' + digit); -} - -template -FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, - buffer& buf) -> int { - // float is passed as double to reduce the number of instantiations. - static_assert(!std::is_same::value, ""); - FMT_ASSERT(value >= 0, "value is negative"); - auto converted_value = convert_float(value); - - const bool fixed = specs.format == float_format::fixed; - if (value <= 0) { // <= instead of == to silence a warning. - if (precision <= 0 || !fixed) { - buf.push_back('0'); - return 0; - } - buf.try_resize(to_unsigned(precision)); - fill_n(buf.data(), precision, '0'); - return -precision; - } - - int exp = 0; - bool use_dragon = true; - unsigned dragon_flags = 0; - if (!is_fast_float()) { - const auto inv_log2_10 = 0.3010299956639812; // 1 / log2(10) - using info = dragonbox::float_info; - const auto f = basic_fp(converted_value); - // Compute exp, an approximate power of 10, such that - // 10^(exp - 1) <= value < 10^exp or 10^exp <= value < 10^(exp + 1). - // This is based on log10(value) == log2(value) / log2(10) and approximation - // of log2(value) by e + num_fraction_bits idea from double-conversion. - exp = static_cast( - std::ceil((f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10)); - dragon_flags = dragon::fixup; - } else if (!is_constant_evaluated() && precision < 0) { - // Use Dragonbox for the shortest format. - if (specs.binary32) { - auto dec = dragonbox::to_decimal(static_cast(value)); - write(buffer_appender(buf), dec.significand); - return dec.exponent; - } - auto dec = dragonbox::to_decimal(static_cast(value)); - write(buffer_appender(buf), dec.significand); - return dec.exponent; - } else { - // Use Grisu + Dragon4 for the given precision: - // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. - const int min_exp = -60; // alpha in Grisu. - int cached_exp10 = 0; // K in Grisu. - fp normalized = normalize(fp(converted_value)); - const auto cached_pow = get_cached_power( - min_exp - (normalized.e + fp::num_significand_bits), cached_exp10); - normalized = normalized * cached_pow; - gen_digits_handler handler{buf.data(), 0, precision, -cached_exp10, fixed}; - if (grisu_gen_digits(normalized, 1, exp, handler) != digits::error && - !is_constant_evaluated()) { - exp += handler.exp10; - buf.try_resize(to_unsigned(handler.size)); - use_dragon = false; - } else { - exp += handler.size - cached_exp10 - 1; - precision = handler.precision; - } - } - if (use_dragon) { - auto f = basic_fp(); - bool is_predecessor_closer = specs.binary32 - ? f.assign(static_cast(value)) - : f.assign(converted_value); - if (is_predecessor_closer) dragon_flags |= dragon::predecessor_closer; - if (fixed) dragon_flags |= dragon::fixed; - // Limit precision to the maximum possible number of significant digits in - // an IEEE754 double because we don't need to generate zeros. - const int max_double_digits = 767; - if (precision > max_double_digits) precision = max_double_digits; - format_dragon(f, dragon_flags, precision, buf, exp); - } - if (!fixed && !specs.showpoint) { - // Remove trailing zeros. - auto num_digits = buf.size(); - while (num_digits > 0 && buf[num_digits - 1] == '0') { - --num_digits; - ++exp; - } - buf.try_resize(num_digits); - } - return exp; -} - -template ::value)> -FMT_CONSTEXPR20 auto write(OutputIt out, T value, - basic_format_specs specs, locale_ref loc = {}) - -> OutputIt { - if (const_check(!is_supported_floating_point(value))) return out; - float_specs fspecs = parse_float_type_spec(specs); - fspecs.sign = specs.sign; - if (detail::signbit(value)) { // value < 0 is false for NaN so use signbit. - fspecs.sign = sign::minus; - value = -value; - } else if (fspecs.sign == sign::minus) { - fspecs.sign = sign::none; - } - - if (!detail::isfinite(value)) - return write_nonfinite(out, detail::isnan(value), specs, fspecs); - - if (specs.align == align::numeric && fspecs.sign) { - auto it = reserve(out, 1); - *it++ = detail::sign(fspecs.sign); - out = base_iterator(out, it); - fspecs.sign = sign::none; - if (specs.width != 0) --specs.width; - } - - memory_buffer buffer; - if (fspecs.format == float_format::hex) { - if (fspecs.sign) buffer.push_back(detail::sign(fspecs.sign)); - snprintf_float(convert_float(value), specs.precision, fspecs, buffer); - return write_bytes(out, {buffer.data(), buffer.size()}, - specs); - } - int precision = specs.precision >= 0 || specs.type == presentation_type::none - ? specs.precision - : 6; - if (fspecs.format == float_format::exp) { - if (precision == max_value()) - throw_format_error("number is too big"); - else - ++precision; - } else if (fspecs.format != float_format::fixed && precision == 0) { - precision = 1; - } - if (const_check(std::is_same())) fspecs.binary32 = true; - int exp = format_float(convert_float(value), precision, fspecs, buffer); - fspecs.precision = precision; - auto f = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; - return write_float(out, f, specs, fspecs, loc); -} - -template ::value)> -FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { - if (is_constant_evaluated()) - return write(out, value, basic_format_specs()); - if (const_check(!is_supported_floating_point(value))) return out; - - auto fspecs = float_specs(); - if (detail::signbit(value)) { - fspecs.sign = sign::minus; - value = -value; - } - - constexpr auto specs = basic_format_specs(); - using floaty = conditional_t::value, double, T>; - using fmt_uint = typename dragonbox::float_info::carrier_uint; - fmt_uint mask = exponent_mask(); - if ((bit_cast(value) & mask) == mask) - return write_nonfinite(out, std::isnan(value), specs, fspecs); - - auto dec = dragonbox::to_decimal(static_cast(value)); - return write_float(out, dec, specs, fspecs, {}); -} - -template ::value && - !is_fast_float::value)> -inline auto write(OutputIt out, T value) -> OutputIt { - return write(out, value, basic_format_specs()); -} - -template -auto write(OutputIt out, monostate, basic_format_specs = {}, - locale_ref = {}) -> OutputIt { - FMT_ASSERT(false, ""); - return out; -} - -template -FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) - -> OutputIt { - auto it = reserve(out, value.size()); - it = copy_str_noinline(value.begin(), value.end(), it); - return base_iterator(out, it); -} - -template ::value)> -constexpr auto write(OutputIt out, const T& value) -> OutputIt { - return write(out, to_string_view(value)); -} - -// FMT_ENABLE_IF() condition separated to workaround an MSVC bug. -template < - typename Char, typename OutputIt, typename T, - bool check = - std::is_enum::value && !std::is_same::value && - mapped_type_constant>::value != - type::custom_type, - FMT_ENABLE_IF(check)> -FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { - return write(out, static_cast>(value)); -} - -template ::value)> -FMT_CONSTEXPR auto write(OutputIt out, T value, - const basic_format_specs& specs = {}, - locale_ref = {}) -> OutputIt { - return specs.type != presentation_type::none && - specs.type != presentation_type::string - ? write(out, value ? 1 : 0, specs, {}) - : write_bytes(out, value ? "true" : "false", specs); -} - -template -FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { - auto it = reserve(out, 1); - *it++ = value; - return base_iterator(out, it); -} - -template -FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value) - -> OutputIt { - if (!value) { - throw_format_error("string pointer is null"); - } else { - out = write(out, basic_string_view(value)); - } - return out; -} - -template ::value)> -auto write(OutputIt out, const T* value, - const basic_format_specs& specs = {}, locale_ref = {}) - -> OutputIt { - check_pointer_type_spec(specs.type, error_handler()); - return write_ptr(out, bit_cast(value), &specs); -} - -// A write overload that handles implicit conversions. -template > -FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> enable_if_t< - std::is_class::value && !is_string::value && - !is_floating_point::value && !std::is_same::value && - !std::is_same().map(value))>::value, - OutputIt> { - return write(out, arg_mapper().map(value)); -} - -template > -FMT_CONSTEXPR auto write(OutputIt out, const T& value) - -> enable_if_t::value == type::custom_type, - OutputIt> { - using formatter_type = - conditional_t::value, - typename Context::template formatter_type, - fallback_formatter>; - auto ctx = Context(out, {}, {}); - return formatter_type().format(value, ctx); -} - -// An argument visitor that formats the argument and writes it via the output -// iterator. It's a class and not a generic lambda for compatibility with C++11. -template struct default_arg_formatter { - using iterator = buffer_appender; - using context = buffer_context; - - iterator out; - basic_format_args args; - locale_ref loc; - - template auto operator()(T value) -> iterator { - return write(out, value); - } - auto operator()(typename basic_format_arg::handle h) -> iterator { - basic_format_parse_context parse_ctx({}); - context format_ctx(out, args, loc); - h.format(parse_ctx, format_ctx); - return format_ctx.out(); - } -}; - -template struct arg_formatter { - using iterator = buffer_appender; - using context = buffer_context; - - iterator out; - const basic_format_specs& specs; - locale_ref locale; - - template - FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator { - return detail::write(out, value, specs, locale); - } - auto operator()(typename basic_format_arg::handle) -> iterator { - // User-defined types are handled separately because they require access - // to the parse context. - return out; - } -}; - -template struct custom_formatter { - basic_format_parse_context& parse_ctx; - buffer_context& ctx; - - void operator()( - typename basic_format_arg>::handle h) const { - h.format(parse_ctx, ctx); - } - template void operator()(T) const {} -}; - -template -using is_integer = - bool_constant::value && !std::is_same::value && - !std::is_same::value && - !std::is_same::value>; - -template class width_checker { - public: - explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} - - template ::value)> - FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { - if (is_negative(value)) handler_.on_error("negative width"); - return static_cast(value); - } - - template ::value)> - FMT_CONSTEXPR auto operator()(T) -> unsigned long long { - handler_.on_error("width is not integer"); - return 0; - } - - private: - ErrorHandler& handler_; -}; - -template class precision_checker { - public: - explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {} - - template ::value)> - FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { - if (is_negative(value)) handler_.on_error("negative precision"); - return static_cast(value); - } - - template ::value)> - FMT_CONSTEXPR auto operator()(T) -> unsigned long long { - handler_.on_error("precision is not integer"); - return 0; - } - - private: - ErrorHandler& handler_; -}; - -template