standalone: VoxelForgeCore link-safe next to Unreal -- internals in vfcore::detail, symbol check
- The copied generator, mesher and Compat stand-ins compile inside vfcore::detail. - CheckCoreSymbols.ps1 (run by Build.ps1) fails on any unqualified Unreal-style symbol in the library: 16,255 on557f6aa, 0 now. A UE 5.7 CoreMinimal.h + VoxelForgeCore.h smoke compile passes. - Output identical: 11 hash rows at W1/W8, routes 5642/5642, look/passage/far audits pass, bad-shell control fails, six captures byte-identical. Cost, measured by me interleaved (8 workers, cold, 4 runs each, first run excluded): 0.963 s vs 0.937 s for557f6aa, ~27 ms in shape compilation; same math, so codegen/inlining. To be found. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
557f6aac30
commit
aeeedaa73d
11 files changed
+461
-87
No files matched your search
+52
-5
@@ -38,6 +38,32 @@ foreach ($Source in $CoreSources) {
|
|||||||
if (-not (Test-Path -LiteralPath $Source -PathType Leaf)) { throw "Required core source is missing: $Source" }
|
if (-not (Test-Path -LiteralPath $Source -PathType Leaf)) { throw "Required core source is missing: $Source" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Keep the canonical source manifest shared with UBT, but compile every copied implementation
|
||||||
|
# source through a generated one-source shim that places its Unreal-shaped internals in a private
|
||||||
|
# namespace. The public API implementation owns its boundary and is compiled directly.
|
||||||
|
$WrapperDir = Join-Path $BuildDir 'wrappers'
|
||||||
|
[IO.Directory]::CreateDirectory($WrapperDir) | Out-Null
|
||||||
|
$CompileSources = [Collections.Generic.List[string]]::new()
|
||||||
|
foreach ($Index in 0..($CoreSources.Count-1)) {
|
||||||
|
$ManifestName = $CoreSourceNames[$Index].Replace('\','/')
|
||||||
|
if ($ManifestName -eq 'Core/src/VoxelForgeCore.cpp') {
|
||||||
|
$CompileSources.Add($CoreSources[$Index])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
$WrapperName = 'vfcore_' + ($ManifestName -replace '[^A-Za-z0-9]+','_')
|
||||||
|
$WrapperPath = Join-Path $WrapperDir ($WrapperName + '.cpp')
|
||||||
|
$SourceInclude = $CoreSources[$Index].Replace('\','/')
|
||||||
|
$WrapperText = @"
|
||||||
|
#include "CoreNamespacePreamble.h"
|
||||||
|
namespace vfcore::detail
|
||||||
|
{
|
||||||
|
#include "$SourceInclude"
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
[IO.File]::WriteAllText($WrapperPath,$WrapperText,[Text.UTF8Encoding]::new($false))
|
||||||
|
$CompileSources.Add($WrapperPath)
|
||||||
|
}
|
||||||
|
|
||||||
$IncludeArgs = @(
|
$IncludeArgs = @(
|
||||||
('/I"{0}"' -f (Join-Path $Root 'Core\include')),
|
('/I"{0}"' -f (Join-Path $Root 'Core\include')),
|
||||||
('/I"{0}"' -f (Join-Path $Root 'Core\src')),
|
('/I"{0}"' -f (Join-Path $Root 'Core\src')),
|
||||||
@@ -48,16 +74,28 @@ $IncludeArgs = @(
|
|||||||
)
|
)
|
||||||
$CommonFlags = @('/nologo','/std:c++20','/O2','/arch:AVX2','/fp:precise','/EHsc','/MP8','/D_CRT_SECURE_NO_WARNINGS') + $IncludeArgs
|
$CommonFlags = @('/nologo','/std:c++20','/O2','/arch:AVX2','/fp:precise','/EHsc','/MP8','/D_CRT_SECURE_NO_WARNINGS') + $IncludeArgs
|
||||||
$CoreRsp = Join-Path $BuildDir 'core-compile.rsp'
|
$CoreRsp = Join-Path $BuildDir 'core-compile.rsp'
|
||||||
$CoreRspLines = $CommonFlags + @('/c') + @($CoreSources | ForEach-Object { '"{0}"' -f $_ })
|
$CoreRspLines = $CommonFlags + @('/c') + @($CompileSources | ForEach-Object { '"{0}"' -f $_ })
|
||||||
[IO.File]::WriteAllLines($CoreRsp,$CoreRspLines,[Text.UTF8Encoding]::new($false))
|
[IO.File]::WriteAllLines($CoreRsp,$CoreRspLines,[Text.UTF8Encoding]::new($false))
|
||||||
|
|
||||||
$Objects = @($CoreSources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) })
|
$Objects = @($CompileSources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) })
|
||||||
$LibRsp = Join-Path $BuildDir 'core-library.rsp'
|
$LibRsp = Join-Path $BuildDir 'core-library.rsp'
|
||||||
[IO.File]::WriteAllLines($LibRsp,@('/nologo',('/OUT:"{0}"' -f $CoreLibrary)) + $Objects,[Text.UTF8Encoding]::new($false))
|
[IO.File]::WriteAllLines($LibRsp,@('/nologo',('/OUT:"{0}"' -f $CoreLibrary)) + $Objects,[Text.UTF8Encoding]::new($false))
|
||||||
|
|
||||||
|
$LegacySources = @()
|
||||||
|
$LegacyObjects = @()
|
||||||
|
$LegacyRsp = Join-Path $BuildDir 'viewer-reference-compile.rsp'
|
||||||
|
if ($Target -eq 'All') {
|
||||||
|
for ($Index=0; $Index -lt $CoreSources.Count; ++$Index) {
|
||||||
|
if ($CoreSourceNames[$Index].Replace('\','/') -eq 'Core/src/VoxelForgeCore.cpp') { continue }
|
||||||
|
$LegacySources += $CoreSources[$Index]
|
||||||
|
}
|
||||||
|
$LegacyObjects = @($LegacySources | ForEach-Object { '"{0}.obj"' -f [IO.Path]::GetFileNameWithoutExtension($_) })
|
||||||
|
$LegacyRspLines = $CommonFlags + @('/c') + @($LegacySources | ForEach-Object { '"{0}"' -f $_ })
|
||||||
|
[IO.File]::WriteAllLines($LegacyRsp,$LegacyRspLines,[Text.UTF8Encoding]::new($false))
|
||||||
|
}
|
||||||
|
|
||||||
$ViewerRsp = Join-Path $BuildDir 'viewer-link.rsp'
|
$ViewerRsp = Join-Path $BuildDir 'viewer-link.rsp'
|
||||||
$ViewerRspLines = $CommonFlags + @(
|
$ViewerRspLines = $CommonFlags + @('/Fe"{0}"' -f $OutputPath) + $LegacyObjects + @(
|
||||||
('/Fe"{0}"' -f $OutputPath),
|
|
||||||
('"{0}"' -f (Join-Path $Root 'Source\StandaloneViewer.cpp')),
|
('"{0}"' -f (Join-Path $Root 'Source\StandaloneViewer.cpp')),
|
||||||
('"{0}"' -f $CoreLibrary),
|
('"{0}"' -f $CoreLibrary),
|
||||||
'/link','d3d11.lib','dxgi.lib','d3dcompiler.lib','user32.lib','gdi32.lib'
|
'/link','d3d11.lib','dxgi.lib','d3dcompiler.lib','user32.lib','gdi32.lib'
|
||||||
@@ -69,11 +107,20 @@ try {
|
|||||||
# One cl process with /MP8 bounds compiler work to eight processes; MSVC, lib.exe, and the
|
# One cl process with /MP8 bounds compiler work to eight processes; MSVC, lib.exe, and the
|
||||||
# viewer link inherit BelowNormal priority and the 0xFF00 process affinity from this host.
|
# viewer link inherit BelowNormal priority and the 0xFF00 process affinity from this host.
|
||||||
$CommandLine = 'call "{0}" >nul && cl.exe @"{1}" && lib.exe @"{2}"' -f $VcVars,$CoreRsp,$LibRsp
|
$CommandLine = 'call "{0}" >nul && cl.exe @"{1}" && lib.exe @"{2}"' -f $VcVars,$CoreRsp,$LibRsp
|
||||||
if ($Target -eq 'All') { $CommandLine += ' && cl.exe @"{0}"' -f $ViewerRsp }
|
|
||||||
& $env:ComSpec /d /s /c $CommandLine
|
& $env:ComSpec /d /s /c $CommandLine
|
||||||
if ($LASTEXITCODE -eq -1073741502) { throw 'A build process exited with 0xC0000142; stopping as required.' }
|
if ($LASTEXITCODE -eq -1073741502) { throw 'A build process exited with 0xC0000142; stopping as required.' }
|
||||||
if ($LASTEXITCODE -ne 0) { throw "VoxelForgeCore build failed with exit code $LASTEXITCODE" }
|
if ($LASTEXITCODE -ne 0) { throw "VoxelForgeCore build failed with exit code $LASTEXITCODE" }
|
||||||
if (-not (Test-Path -LiteralPath $CoreLibrary -PathType Leaf)) { throw "Core library was not produced: $CoreLibrary" }
|
if (-not (Test-Path -LiteralPath $CoreLibrary -PathType Leaf)) { throw "Core library was not produced: $CoreLibrary" }
|
||||||
|
$SymbolCheck = Join-Path $Root 'Core\CheckCoreSymbols.ps1'
|
||||||
|
& $SymbolCheck -LibraryPath $CoreLibrary
|
||||||
|
if ($Target -eq 'All') {
|
||||||
|
# The standalone viewer retains a global compatibility build only for legacy comparison
|
||||||
|
# and audits. Those objects are linked into the viewer executable, never into Core.lib.
|
||||||
|
$CommandLine = 'call "{0}" >nul && cl.exe @"{1}" && cl.exe @"{2}"' -f $VcVars,$LegacyRsp,$ViewerRsp
|
||||||
|
& $env:ComSpec /d /s /c $CommandLine
|
||||||
|
if ($LASTEXITCODE -eq -1073741502) { throw 'A build process exited with 0xC0000142; stopping as required.' }
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Standalone viewer build failed with exit code $LASTEXITCODE" }
|
||||||
|
}
|
||||||
if ($Target -eq 'All' -and -not (Test-Path -LiteralPath $OutputPath -PathType Leaf)) { throw "Viewer was not produced: $OutputPath" }
|
if ($Target -eq 'All' -and -not (Test-Path -LiteralPath $OutputPath -PathType Leaf)) { throw "Viewer was not produced: $OutputPath" }
|
||||||
} finally {
|
} finally {
|
||||||
Pop-Location
|
Pop-Location
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace VFJson
|
namespace VFJson
|
||||||
{
|
{
|
||||||
@@ -27,7 +37,7 @@ namespace VFJson
|
|||||||
return It == O.end() ? Null : It->second;
|
return It == O.end() ? Null : It->second;
|
||||||
}
|
}
|
||||||
double Number(double Fallback = 0.0) const { return IsNumber() ? std::get<double>(Data) : Fallback; }
|
double Number(double Fallback = 0.0) const { return IsNumber() ? std::get<double>(Data) : Fallback; }
|
||||||
int64 Integer(int64 Fallback = 0) const { return IsNumber() ? static_cast<int64>(std::get<double>(Data)) : Fallback; }
|
std::int64_t Integer(std::int64_t Fallback = 0) const { return IsNumber() ? static_cast<std::int64_t>(std::get<double>(Data)) : Fallback; }
|
||||||
bool Boolean(bool Fallback = false) const { return std::holds_alternative<bool>(Data) ? std::get<bool>(Data) : Fallback; }
|
bool Boolean(bool Fallback = false) const { return std::holds_alternative<bool>(Data) ? std::get<bool>(Data) : Fallback; }
|
||||||
const std::string& String() const { static const std::string Empty; return IsString() ? std::get<std::string>(Data) : Empty; }
|
const std::string& String() const { static const std::string Empty; return IsString() ? std::get<std::string>(Data) : Empty; }
|
||||||
};
|
};
|
||||||
@@ -49,23 +59,23 @@ namespace VFJson
|
|||||||
size_t Position = 0;
|
size_t Position = 0;
|
||||||
void SkipSpace() { while (Position < Text.size() && static_cast<unsigned char>(Text[Position]) <= 0x20) ++Position; }
|
void SkipSpace() { while (Position < Text.size() && static_cast<unsigned char>(Text[Position]) <= 0x20) ++Position; }
|
||||||
bool Consume(char C) { SkipSpace(); if (Position < Text.size() && Text[Position] == C) { ++Position; return true; } return false; }
|
bool Consume(char C) { SkipSpace(); if (Position < Text.size() && Text[Position] == C) { ++Position; return true; } return false; }
|
||||||
static void AppendUtf8(std::string& Out, uint32 Code)
|
static void AppendUtf8(std::string& Out, std::uint32_t Code)
|
||||||
{
|
{
|
||||||
if (Code <= 0x7f) Out.push_back(static_cast<char>(Code));
|
if (Code <= 0x7f) Out.push_back(static_cast<char>(Code));
|
||||||
else if (Code <= 0x7ff) { Out.push_back(static_cast<char>(0xc0 | (Code >> 6))); Out.push_back(static_cast<char>(0x80 | (Code & 0x3f))); }
|
else if (Code <= 0x7ff) { Out.push_back(static_cast<char>(0xc0 | (Code >> 6))); Out.push_back(static_cast<char>(0x80 | (Code & 0x3f))); }
|
||||||
else if (Code <= 0xffff) { Out.push_back(static_cast<char>(0xe0 | (Code >> 12))); Out.push_back(static_cast<char>(0x80 | ((Code >> 6) & 0x3f))); Out.push_back(static_cast<char>(0x80 | (Code & 0x3f))); }
|
else if (Code <= 0xffff) { Out.push_back(static_cast<char>(0xe0 | (Code >> 12))); Out.push_back(static_cast<char>(0x80 | ((Code >> 6) & 0x3f))); Out.push_back(static_cast<char>(0x80 | (Code & 0x3f))); }
|
||||||
else { Out.push_back(static_cast<char>(0xf0 | (Code >> 18))); Out.push_back(static_cast<char>(0x80 | ((Code >> 12) & 0x3f))); Out.push_back(static_cast<char>(0x80 | ((Code >> 6) & 0x3f))); Out.push_back(static_cast<char>(0x80 | (Code & 0x3f))); }
|
else { Out.push_back(static_cast<char>(0xf0 | (Code >> 18))); Out.push_back(static_cast<char>(0x80 | ((Code >> 12) & 0x3f))); Out.push_back(static_cast<char>(0x80 | ((Code >> 6) & 0x3f))); Out.push_back(static_cast<char>(0x80 | (Code & 0x3f))); }
|
||||||
}
|
}
|
||||||
bool Hex4(uint32& Out)
|
bool Hex4(std::uint32_t& Out)
|
||||||
{
|
{
|
||||||
if (Position + 4 > Text.size()) return false;
|
if (Position + 4 > Text.size()) return false;
|
||||||
Out = 0;
|
Out = 0;
|
||||||
for (int32 I = 0; I < 4; ++I)
|
for (std::int32_t I = 0; I < 4; ++I)
|
||||||
{
|
{
|
||||||
const char C = Text[Position++];
|
const char C = Text[Position++];
|
||||||
const int32 N = (C >= '0' && C <= '9') ? C - '0' : (C >= 'a' && C <= 'f') ? C - 'a' + 10 : (C >= 'A' && C <= 'F') ? C - 'A' + 10 : -1;
|
const std::int32_t N = (C >= '0' && C <= '9') ? C - '0' : (C >= 'a' && C <= 'f') ? C - 'a' + 10 : (C >= 'A' && C <= 'F') ? C - 'A' + 10 : -1;
|
||||||
if (N < 0) return false;
|
if (N < 0) return false;
|
||||||
Out = (Out << 4) | static_cast<uint32>(N);
|
Out = (Out << 4) | static_cast<std::uint32_t>(N);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -89,11 +99,11 @@ namespace VFJson
|
|||||||
case 'r': Out.push_back('\r'); break; case 't': Out.push_back('\t'); break;
|
case 'r': Out.push_back('\r'); break; case 't': Out.push_back('\t'); break;
|
||||||
case 'u':
|
case 'u':
|
||||||
{
|
{
|
||||||
uint32 Code = 0; if (!Hex4(Code)) { Error = "invalid JSON unicode escape"; return false; }
|
std::uint32_t Code = 0; if (!Hex4(Code)) { Error = "invalid JSON unicode escape"; return false; }
|
||||||
if (Code >= 0xd800 && Code <= 0xdbff)
|
if (Code >= 0xd800 && Code <= 0xdbff)
|
||||||
{
|
{
|
||||||
if (Position + 2 > Text.size() || Text[Position++] != '\\' || Text[Position++] != 'u') { Error = "missing JSON low surrogate"; return false; }
|
if (Position + 2 > Text.size() || Text[Position++] != '\\' || Text[Position++] != 'u') { Error = "missing JSON low surrogate"; return false; }
|
||||||
uint32 Low = 0; if (!Hex4(Low) || Low < 0xdc00 || Low > 0xdfff) { Error = "invalid JSON low surrogate"; return false; }
|
std::uint32_t Low = 0; if (!Hex4(Low) || Low < 0xdc00 || Low > 0xdfff) { Error = "invalid JSON low surrogate"; return false; }
|
||||||
Code = 0x10000 + ((Code - 0xd800) << 10) + (Low - 0xdc00);
|
Code = 0x10000 + ((Code - 0xd800) << 10) + (Low - 0xdc00);
|
||||||
}
|
}
|
||||||
AppendUtf8(Out, Code); break;
|
AppendUtf8(Out, Code); break;
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ Public header: `Core/include/VoxelForgeCore/VoxelForgeCore.h` (C++20, standard l
|
|||||||
Build the static library and viewer with `Build.ps1 -Target All`, or only the library with
|
Build the static library and viewer with `Build.ps1 -Target All`, or only the library with
|
||||||
`Build.ps1 -Target Core`.
|
`Build.ps1 -Target Core`.
|
||||||
|
|
||||||
|
The public header uses only standard C++ types and can be included after Unreal's `CoreMinimal.h`.
|
||||||
|
`Core/verification/UnrealHeaderCoexistence.cpp` is the compile smoke check against the installed
|
||||||
|
Unreal 5.7 headers. Copied generator and compatibility declarations stay in the private
|
||||||
|
`vfcore::detail` implementation and do not appear in this API.
|
||||||
|
|
||||||
## Units and ownership
|
## Units and ownership
|
||||||
|
|
||||||
- `Vec3d` query positions and `Vec3i` voxel coordinates are in **voxels**.
|
- `Vec3d` query positions and `Vec3i` voxel coordinates are in **voxels**.
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$LibraryPath,
|
||||||
|
[string]$ResultPath
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$script:Root = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
||||||
|
$LibraryPath = [IO.Path]::GetFullPath($LibraryPath)
|
||||||
|
if (-not (Test-Path -LiteralPath $LibraryPath -PathType Leaf)) {
|
||||||
|
throw "Core library not found: $LibraryPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
$VsBinRoots = @(Get-ChildItem -LiteralPath 'E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC' -Directory -ErrorAction Stop |
|
||||||
|
ForEach-Object { Join-Path $_.FullName 'bin\Hostx64\x64' } |
|
||||||
|
Where-Object { Test-Path -LiteralPath (Join-Path $_ 'dumpbin.exe') })
|
||||||
|
if ($VsBinRoots.Count -eq 0) { throw 'dumpbin.exe was not found in the Visual Studio toolchain.' }
|
||||||
|
$Dumpbin = Join-Path $VsBinRoots[-1] 'dumpbin.exe'
|
||||||
|
|
||||||
|
# Build the forbidden set from the compatibility and copied Unreal-facing declarations so the
|
||||||
|
# check covers the complete private type surface instead of a short hand-maintained example list.
|
||||||
|
$ScanRoots = @(
|
||||||
|
(Join-Path $script:Root 'Compat'),
|
||||||
|
(Join-Path $script:Root 'Source'),
|
||||||
|
(Join-Path $script:Root 'Core\src')
|
||||||
|
)
|
||||||
|
$TypeNames = [Collections.Generic.HashSet[string]]::new([StringComparer]::Ordinal)
|
||||||
|
foreach ($ScanRoot in $ScanRoots) {
|
||||||
|
foreach ($File in Get-ChildItem -LiteralPath $ScanRoot -Recurse -File -Include *.h,*.hpp,*.cpp) {
|
||||||
|
foreach ($Match in [regex]::Matches([IO.File]::ReadAllText($File.FullName),
|
||||||
|
'(?m)^\s*(?:class|struct|enum(?:\s+class)?|using)\s+([FTUE][A-Z][A-Za-z0-9_]*)\b')) {
|
||||||
|
[void]$TypeNames.Add($Match.Groups[1].Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($TypeNames.Count -eq 0) { throw 'Could not derive Unreal-style type names from the copied implementation.' }
|
||||||
|
|
||||||
|
$Dump = @(& $Dumpbin /SYMBOLS $LibraryPath 2>&1)
|
||||||
|
if ($LASTEXITCODE -eq -1073741502) { throw 'dumpbin.exe exited with 0xC0000142; stopping as required.' }
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "dumpbin.exe failed with exit code $LASTEXITCODE" }
|
||||||
|
if ($Dump.Count -lt 1000) {
|
||||||
|
throw "dumpbin returned only $($Dump.Count) lines; Core object symbols are not visible enough to verify."
|
||||||
|
}
|
||||||
|
|
||||||
|
$Violations = [Collections.Generic.List[string]]::new()
|
||||||
|
$Alternation = ($TypeNames | Sort-Object { $_.Length } -Descending | ForEach-Object { [regex]::Escape($_) }) -join '|'
|
||||||
|
$GlobalTypePattern = [regex]::new('(?<![A-Za-z0-9_:])(?:' + $Alternation + ')(?![A-Za-z0-9_])',
|
||||||
|
[Text.RegularExpressions.RegexOptions]::Compiled)
|
||||||
|
$PrivateTypePattern = [regex]::new('vfcore::detail::(?:class\s+|struct\s+|enum\s+)?(?:' + $Alternation + ')(?![A-Za-z0-9_])',
|
||||||
|
[Text.RegularExpressions.RegexOptions]::Compiled)
|
||||||
|
$PrivateDestructorPattern = [regex]::new('~(?:' + $Alternation + ')(?![A-Za-z0-9_])',
|
||||||
|
[Text.RegularExpressions.RegexOptions]::Compiled)
|
||||||
|
foreach ($Line in $Dump) {
|
||||||
|
if ($Line -notmatch '\|') { continue }
|
||||||
|
$Symbol = ($Line -split '\|',2)[1]
|
||||||
|
$DemangleOpen = $Symbol.IndexOf('(')
|
||||||
|
if ($DemangleOpen -lt 0) { continue }
|
||||||
|
$Demangled = $Symbol.Substring($DemangleOpen+1)
|
||||||
|
# dumpbin repeats decorated-only linker helpers in parentheses when it has no readable
|
||||||
|
# demangled form. Ignore those and remove known vfcore-qualified type spellings, including the
|
||||||
|
# repeated destructor spelling after "::~", before looking for global type names.
|
||||||
|
if ($Demangled -match '^\s*[?$]') { continue }
|
||||||
|
$Demangled = $PrivateTypePattern.Replace($Demangled,'')
|
||||||
|
$Demangled = $PrivateDestructorPattern.Replace($Demangled,'')
|
||||||
|
# A type preceded by a scope separator belongs to a namespace. An unqualified instance in
|
||||||
|
# the demangled dump is a global Unreal-style type and can ODR-collide with the engine.
|
||||||
|
if ($GlobalTypePattern.IsMatch($Demangled)) {
|
||||||
|
$Violations.Add($Line.Trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($Violations.Count -gt 0) {
|
||||||
|
$ResultLines = @("CORE_LINK_SAFETY_FAIL global Unreal-style symbols: $($Violations.Count)") +
|
||||||
|
@($Violations | Select-Object -First 40)
|
||||||
|
if ($ResultPath) { [IO.File]::WriteAllLines([IO.Path]::GetFullPath($ResultPath),$ResultLines) }
|
||||||
|
$ResultLines | ForEach-Object { Write-Output $_ }
|
||||||
|
throw "Core archive contains $($Violations.Count) unqualified Unreal-style type symbols."
|
||||||
|
}
|
||||||
|
|
||||||
|
$ResultLines = @("CORE_LINK_SAFETY_PASS checked $($Dump.Count) dumpbin lines against $($TypeNames.Count) copied Unreal-style type names.")
|
||||||
|
if ($ResultPath) { [IO.File]::WriteAllLines([IO.Path]::GetFullPath($ResultPath),$ResultLines) }
|
||||||
|
$ResultLines | ForEach-Object { Write-Output $_ }
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Every standard or platform header used by the copied implementation must be included before
|
||||||
|
// its wrapper opens vfcore::detail. This prevents system declarations from being nested in the
|
||||||
|
// private namespace when the implementation includes its Unreal-shaped compatibility headers.
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
#include <bit>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cctype>
|
||||||
|
#include <charconv>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstdarg>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <functional>
|
||||||
|
#include <immintrin.h>
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <limits>
|
||||||
|
#include <map>
|
||||||
|
#include <math.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <memory_resource>
|
||||||
|
#include <mutex>
|
||||||
|
#include <new>
|
||||||
|
#include <numeric>
|
||||||
|
#include <optional>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <span>
|
||||||
|
#include <sstream>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <thread>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// VFJson is a small, stateless standalone input parser with no Unreal-shaped names. Keeping its
|
||||||
|
// value tree shared lets the viewer resolve its reference world and Core snapshot from one parse.
|
||||||
|
#include "Json.h"
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "OwnerWorldLoader.h"
|
#include "Json.h"
|
||||||
#include "VoxelForgeCore/VoxelForgeCore.h"
|
#include "VoxelForgeCore/VoxelForgeCore.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <string>
|
||||||
|
|
||||||
namespace VoxelForgeCore
|
namespace VoxelForgeCore
|
||||||
{
|
{
|
||||||
// Private standalone adapter: keeps the compatibility holders behind the public value API.
|
// Standalone-only bridge. Core parses into its namespaced compatibility snapshot and applies
|
||||||
ResolvedWorldSnapshot MakeStandaloneSnapshot(std::shared_ptr<FStandaloneOwnerWorld> owner);
|
// the same command-line overrides as the viewer's reference loader.
|
||||||
|
ResolvedWorldSnapshot MakeStandaloneSnapshotFromParsedJson(const VFJson::Value& jsonRoot,
|
||||||
|
std::int32_t seed,std::int32_t clipRadius,std::int32_t maxLodOverride,
|
||||||
|
bool roundTunnels,bool enhancedTunnelWalls,std::string& error);
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,18 @@
|
|||||||
|
#include "CoreNamespacePreamble.h"
|
||||||
#include "StandaloneSnapshotAdapter.h"
|
#include "StandaloneSnapshotAdapter.h"
|
||||||
|
#include "VoxelForgeCore/VoxelForgeCore.h"
|
||||||
|
|
||||||
|
namespace vfcore::detail
|
||||||
|
{
|
||||||
#include "FarShellsInternal.h"
|
#include "FarShellsInternal.h"
|
||||||
#include "VoxelClipmapDesiredTiles.h"
|
#include "VoxelClipmapDesiredTiles.h"
|
||||||
#include "VoxelMarchingCubesMesher.h"
|
#include "VoxelMarchingCubesMesher.h"
|
||||||
#include "VoxelForgeCore/VoxelForgeCore.h"
|
}
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <bit>
|
|
||||||
#include <cmath>
|
|
||||||
#include <cstring>
|
|
||||||
#include <map>
|
|
||||||
#include <numeric>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
namespace VoxelForgeCore
|
namespace VoxelForgeCore
|
||||||
{
|
{
|
||||||
|
using namespace ::vfcore::detail;
|
||||||
|
|
||||||
struct ResolvedWorldSnapshot::Data
|
struct ResolvedWorldSnapshot::Data
|
||||||
{
|
{
|
||||||
std::shared_ptr<FStandaloneOwnerWorld> owner;
|
std::shared_ptr<FStandaloneOwnerWorld> owner;
|
||||||
@@ -185,8 +183,36 @@ namespace VoxelForgeCore
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ResolvedWorldSnapshot MakeStandaloneSnapshot(std::shared_ptr<FStandaloneOwnerWorld> owner)
|
ResolvedWorldSnapshot MakeStandaloneSnapshotFromParsedJson(const VFJson::Value& jsonRoot,
|
||||||
{ return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner)); }
|
std::int32_t seed,std::int32_t clipRadius,std::int32_t maxLodOverride,
|
||||||
|
bool roundTunnels,bool enhancedTunnelWalls,std::string& error)
|
||||||
|
{
|
||||||
|
auto owner=std::make_shared<FStandaloneOwnerWorld>();
|
||||||
|
if(!StandaloneOwnerLoader::Resolve(jsonRoot,*owner,error))return {};
|
||||||
|
|
||||||
|
const bool seedChanged=owner->Seed!=seed;
|
||||||
|
owner->Settings->Seed=seed;
|
||||||
|
owner->Seed=seed;
|
||||||
|
owner->ClipRadius=clipRadius;
|
||||||
|
owner->Settings->ClipRadius=clipRadius;
|
||||||
|
if(seedChanged)
|
||||||
|
{
|
||||||
|
owner->Manager=std::make_unique<UVoxelStrateManager>();
|
||||||
|
if(!owner->Manager->Initialize(owner->Settings.get(),seed))
|
||||||
|
{error="standalone copied strate manager failed while applying viewer settings";return {};}
|
||||||
|
owner->Generator=std::make_unique<UVoxelGenerator>();
|
||||||
|
owner->Generator->InitializeSettings(owner->Settings.get());
|
||||||
|
owner->Generator->SetStrateManager(owner->Manager.get());
|
||||||
|
}
|
||||||
|
owner->Generator->bStandaloneResearchRoundTunnels=roundTunnels;
|
||||||
|
owner->Generator->bStandaloneResearchEnhancedWalls=enhancedTunnelWalls;
|
||||||
|
if(maxLodOverride>=0)
|
||||||
|
{
|
||||||
|
owner->MaxClipLevel=maxLodOverride;
|
||||||
|
owner->Settings->MaxClipLevel=maxLodOverride;
|
||||||
|
}
|
||||||
|
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner));
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<ResolvedWorldSnapshot> ResolvedWorldSnapshot::FromJson(
|
std::optional<ResolvedWorldSnapshot> ResolvedWorldSnapshot::FromJson(
|
||||||
std::string_view json,std::string& error)
|
std::string_view json,std::string& error)
|
||||||
|
|||||||
@@ -23,9 +23,44 @@ changed.
|
|||||||
tile to `World::generateTile`. Shape-world `--stream-benchmark`, `--stream-pipeline`, edit,
|
tile to `World::generateTile`. Shape-world `--stream-benchmark`, `--stream-pipeline`, edit,
|
||||||
audit, and `--benchmark-level` runs use the Core world. Explicit evaluator and owner-comparison
|
audit, and `--benchmark-level` runs use the Core world. Explicit evaluator and owner-comparison
|
||||||
modes retain the compatibility generator only as the comparison side of those diagnostics.
|
modes retain the compatibility generator only as the comparison side of those diagnostics.
|
||||||
- The build manifest compiles the copied CPU generator and mesher with the private compatibility
|
- The build compiles the copied CPU generator and mesher with the private compatibility headers
|
||||||
headers under `Compat`. Those headers and their Unreal-shaped stand-ins are implementation
|
under `Compat`. Those headers and their Unreal-shaped stand-ins are implementation details. They
|
||||||
details. They are absent from the public include tree and public API.
|
are absent from the public include tree and public API.
|
||||||
|
|
||||||
|
## Implementation namespace and Unreal link safety
|
||||||
|
|
||||||
|
The public `VoxelForgeCore.h` surface contains only standard C++ declarations. The copied
|
||||||
|
generator, mesher, and compatibility implementation compile under `vfcore::detail`, so engine
|
||||||
|
symbols such as `FVector`, `TArray`, `FMath`, and `UObject`-shaped stand-ins do not acquire the same
|
||||||
|
linker names as Unreal's definitions. `Core/src/VoxelForgeCore.cpp` owns the public API boundary;
|
||||||
|
it opens `vfcore::detail` only around its private implementation includes, then defines the
|
||||||
|
`VoxelForgeCore` API outside that namespace.
|
||||||
|
|
||||||
|
For each non-boundary source in `Core/core_sources.txt`, `Build.ps1` writes a one-source shim under
|
||||||
|
`Build/VoxelForgeCore/wrappers`. The shim includes `CoreNamespacePreamble.h` before opening
|
||||||
|
`vfcore::detail`, then includes the canonical source path from the manifest. The preamble loads
|
||||||
|
the standard/platform headers and stateless JSON parser before namespace entry, preventing system
|
||||||
|
declarations from being accidentally nested in `vfcore::detail`. The `VoxelForgeCore.cpp` row is
|
||||||
|
compiled directly because it sets up its own public/private namespace boundary. Keep one
|
||||||
|
implementation source per shim.
|
||||||
|
|
||||||
|
`Compat` still defines Unreal-shaped types and a few Unreal-named macros for the copied code, but
|
||||||
|
those declarations live in private implementation translation units only. The UBT module must
|
||||||
|
disable unity builds and PCHs for these shims, and must not force-include Unreal headers into
|
||||||
|
them. This keeps compatibility macros local to one translation unit. Normal plugin adapter
|
||||||
|
translation units may include `CoreMinimal.h` and the public `VoxelForgeCore.h` together; the
|
||||||
|
public header does not include any compatibility header or Unreal declaration.
|
||||||
|
|
||||||
|
`Build.ps1` runs `Core/CheckCoreSymbols.ps1` on the completed static library. The check invokes
|
||||||
|
`dumpbin /SYMBOLS`, derives copied Unreal-style type names from `Compat`, `Source`, and `Core/src`,
|
||||||
|
and fails on an unqualified global occurrence. It also fails closed if `dumpbin` returns too little
|
||||||
|
symbol data to inspect. Rebuilding the `557f6aa` manifest directly and running the same checker
|
||||||
|
found 16,255 unqualified Unreal-style type symbols; the namespaced build checks 177,569
|
||||||
|
symbol-table lines against 370 type names with no violations. The baseline and current logs are
|
||||||
|
retained in `Core/verification/link_safety_557f6aa_final.log` and
|
||||||
|
`Core/verification/link_safety_final.log`. `Core/verification/UnrealHeaderCoexistence.cpp` was
|
||||||
|
also compiled against the installed Unreal 5.7 `CoreMinimal.h` and the public header; see
|
||||||
|
`Core/verification/unreal_header_smoke_final.log`.
|
||||||
|
|
||||||
## API and ownership
|
## API and ownership
|
||||||
|
|
||||||
@@ -53,22 +88,46 @@ cancellation/version policy. Every client must apply the same sequence and brush
|
|||||||
|
|
||||||
## Unreal integration: UBT module
|
## Unreal integration: UBT module
|
||||||
|
|
||||||
Add a `VoxelForgeCore` runtime module to the plugin. Its `Build.cs` sets
|
Add a `VoxelForgeCore` runtime module to the plugin and make the existing `VoxelForge` module depend
|
||||||
`CppStandardVersion.Cpp20`, exports only `Standalone/Core/include`, and keeps these private include
|
on it. Its `Build.cs` sets C++20, exports only `Standalone/Core/include`, and keeps the Standalone
|
||||||
roots available to the core implementation:
|
root plus these private include roots available to the core implementation:
|
||||||
|
|
||||||
|
- `Standalone`
|
||||||
- `Standalone/Core/src`
|
- `Standalone/Core/src`
|
||||||
- `Standalone/Compat`
|
- `Standalone/Compat`
|
||||||
- `Standalone/Source`
|
- `Standalone/Source`
|
||||||
- `Standalone/Source/VoxelForge/Public`
|
- `Standalone/Source/VoxelForge/Public`
|
||||||
- `Standalone/Source/VoxelForge/Private`
|
- `Standalone/Source/VoxelForge/Private`
|
||||||
|
|
||||||
|
Set `PCHUsage = PCHUsageMode.NoPCHs` and `bUseUnity = false` for the Core module. For example:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
string PluginRoot = Path.GetFullPath(Path.Combine(ModuleDirectory, "../.."));
|
||||||
|
string Standalone = Path.Combine(PluginRoot, "Standalone");
|
||||||
|
PublicIncludePaths.Add(Path.Combine(Standalone, "Core", "include"));
|
||||||
|
PrivateIncludePaths.AddRange(new[] {
|
||||||
|
Standalone,
|
||||||
|
Path.Combine(Standalone, "Core", "src"),
|
||||||
|
Path.Combine(Standalone, "Compat"),
|
||||||
|
Path.Combine(Standalone, "Source"),
|
||||||
|
Path.Combine(Standalone, "Source", "VoxelForge", "Public"),
|
||||||
|
Path.Combine(Standalone, "Source", "VoxelForge", "Private")
|
||||||
|
});
|
||||||
|
CppStandard = CppStandardVersion.Cpp20;
|
||||||
|
PCHUsage = PCHUsageMode.NoPCHs;
|
||||||
|
bUseUnity = false;
|
||||||
|
```
|
||||||
|
|
||||||
UBT discovers translation units under a module's `Source` tree. Add one thin `.cpp` include shim per
|
UBT discovers translation units under a module's `Source` tree. Add one thin `.cpp` include shim per
|
||||||
row of `Core/core_sources.txt`, with each shim including exactly one canonical source file by a
|
row of `Core/core_sources.txt`, with each shim including exactly one canonical source file by a
|
||||||
relative path into `Standalone`. Keeping one included source per shim preserves the translation
|
path rooted at the private `Standalone` include path. For every implementation row except
|
||||||
unit boundaries used by `Build.ps1`. A manifest check in the UBT module should fail when a manifest
|
`Core/src/VoxelForgeCore.cpp`, the shim includes `CoreNamespacePreamble.h`, opens
|
||||||
row lacks a shim or a shim names a different file. Do not copy implementation files into the
|
`namespace vfcore::detail`, then includes that one source. The special API row gets a shim that
|
||||||
module. The standalone and UBT builds then compile the same source bytes.
|
includes `Core/src/VoxelForgeCore.cpp` directly; that source establishes its own namespace
|
||||||
|
boundary. Keeping one included source per shim preserves the translation-unit boundaries used by
|
||||||
|
`Build.ps1`. A manifest check in the UBT module should fail when a row lacks a shim, a shim names a
|
||||||
|
different file, or an extra shim exists. Do not copy implementation files into the module. The
|
||||||
|
standalone and UBT builds then compile the same canonical source bytes.
|
||||||
|
|
||||||
The existing `VoxelForge` module owns the adapter and calls the new module. Its adapter runs on the
|
The existing `VoxelForge` module owns the adapter and calls the new module. Its adapter runs on the
|
||||||
game thread, resolves soft references and asset defaults, preserves array order, and serializes
|
game thread, resolves soft references and asset defaults, preserves array order, and serializes
|
||||||
@@ -113,22 +172,40 @@ creation, or rendering into the library.
|
|||||||
|
|
||||||
## Acceptance record
|
## Acceptance record
|
||||||
|
|
||||||
The retained Round 11 executable is the reference at checkout `631dd57`. The final Core build
|
The `557f6aa` commit is the strict output baseline. The rebuilt namespaced Core matches all 11
|
||||||
matched its 11 hash rows (full detail, whole view and LOD0–8) at both W1 and W8. It retained the
|
full-detail, whole-view and LOD0–8 hash rows at W1 and W8, with W1/W8 parity. The fresh records are
|
||||||
5,642/5,642 route audit, passed the shape/look and far-survival audits, and the damaged-shell control
|
`Core/verification/last_standard_hash_w1.stdout.log`,
|
||||||
still reports 512 removed triangles, 16 missing boundary surfaces, 16 holes, and exit 14. Six
|
`Core/verification/last_standard_hash_w8.stdout.log`, and
|
||||||
fixed 1280×800 captures compare byte-for-byte with the Round 11 references. The final single-tile
|
`Core/verification/final_rebuilt_hash_parity.log`.
|
||||||
|
|
||||||
|
The look and shape audits pass, and walkability remains 5,642/5,642. The dedicated passage-camera
|
||||||
|
detail audit passes at 13,271 passage samples with a detail ratio of 1.152. Far survival passes
|
||||||
|
with 7,200 surfaces, zero missing surfaces or holes, and 10,976 far connectors. The damaged-shell
|
||||||
|
control removes 512 triangles, reports 16 missing boundary surfaces and 16 holes, and exits 14.
|
||||||
|
The final rebuilt logs are `Core/verification/final_rebuilt_shape_audit.stdout.log`,
|
||||||
|
`Core/verification/final_rebuilt_passage_camera_audit.stdout.log`,
|
||||||
|
`Core/verification/final_rebuilt_far_survival.stdout.log`, and
|
||||||
|
`Core/verification/final_rebuilt_bad_shell_control.stdout.log`.
|
||||||
|
|
||||||
|
The six fixed 1280×800 views were recaptured from the final rebuilt viewer and compare byte-for-byte
|
||||||
|
with the prior Core captures. Their images are `Core/verification/core_final_rebuild_*.png`; the SHA-256
|
||||||
|
results are in `Core/verification/final_rebuilt_capture_parity.log`. The single-tile
|
||||||
`--shape-world --benchmark-level=0` path reports `generator=VoxelForgeCore`.
|
`--shape-world --benchmark-level=0` path reports `generator=VoxelForgeCore`.
|
||||||
|
|
||||||
The final no-hash, cold W8 direct full-payload benchmark was interleaved
|
The link-safety check demonstrates the failure before the change and the pass after it. Running it
|
||||||
reference/core/core/reference with `--stream-benchmark --shape-world --force-generate --workers=8
|
on the `557f6aa` library finds 16,255 unqualified Unreal-style symbols; the rebuilt Core library
|
||||||
--clip-radius=3`. Process wall means were 1.041 s for the reference (1.035, 1.047) and 1.0245 s
|
passes after checking 177,569 `dumpbin /SYMBOLS` lines against 370 copied Unreal-style type names.
|
||||||
for Core (1.037, 1.012), 16.5 ms faster. Each run generated 2,226 tiles with the full 110.90 MiB
|
`Core/verification/link_safety_557f6aa_final.log` and
|
||||||
payload and zero cache hits.
|
`Core/verification/link_safety_final.log` retain the outputs. A smoke translation unit that
|
||||||
|
includes the installed Unreal 5.7 `CoreMinimal.h` and the public `VoxelForgeCore.h` also compiles;
|
||||||
|
see `Core/verification/unreal_header_smoke_final.log`.
|
||||||
|
|
||||||
The separate neighborhood-first `--stream-pipeline` benchmark was repeated in eight interleaved
|
**Speed gate: not met.** The no-hash, cold W8 `--stream-pipeline --shape-world --force-generate`
|
||||||
processes per build. Whole-view-ready means were 0.887 s for the reference (0.878–0.899 s) and
|
check was run in four interleaved baseline/Core pairs, each generating 2,226 tiles and the full
|
||||||
0.894 s for Core (0.880–0.932 s). The 7 ms mean difference is smaller than the 54 ms combined
|
110.90 MiB mesh payload with no cache hits. `whole_view_ready` averaged 0.89175 s for the exact
|
||||||
run-to-run range; Core reached the 27-tile neighborhood and 960-tile full-detail milestones at
|
`557f6aa` executable and 0.92050 s for the namespaced build, a 28.8 ms (3.2%) regression. Stream
|
||||||
comparable times. Direct benchmark, pipeline, audit, and capture logs are retained in
|
wall time was effectively unchanged at 0.46000 s and 0.46025 s; the difference was in shape
|
||||||
`Core/verification`.
|
compilation, including `intervals+signs` averaging 95.7 ms on baseline and 114.9 ms on Core. The
|
||||||
|
raw cold runs are in `Core/verification/final_rebuilt_pipeline_*.stdout.log`. API-only `/Ob3` and
|
||||||
|
`/Ob1` trials also retained hash parity but were slower, so the build is restored to `/O2 /MP8`.
|
||||||
|
The required “no slower” acceptance condition remains outstanding.
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <iterator>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
@@ -351,6 +352,7 @@ namespace
|
|||||||
std::chrono::steady_clock::time_point FullShapeBuildStart{};
|
std::chrono::steady_clock::time_point FullShapeBuildStart{};
|
||||||
FStandaloneMeshCache MeshCache;
|
FStandaloneMeshCache MeshCache;
|
||||||
std::string DataPath;
|
std::string DataPath;
|
||||||
|
VFJson::Value OwnerJsonRoot;
|
||||||
bool bHasCameraOverride=false;
|
bool bHasCameraOverride=false;
|
||||||
V3 CameraPositionOverride{};
|
V3 CameraPositionOverride{};
|
||||||
float CameraYawOverride=0.0f,CameraPitchOverride=0.0f;
|
float CameraYawOverride=0.0f,CameraPitchOverride=0.0f;
|
||||||
@@ -656,7 +658,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
ShapeWorld.reset();
|
ShapeWorld.reset();
|
||||||
Snapshot={};
|
Snapshot={};
|
||||||
World=std::make_shared<FStandaloneOwnerWorld>();
|
World=std::make_shared<FStandaloneOwnerWorld>();
|
||||||
if(!StandaloneOwnerLoader::Load(DataPath,*World,Error))return false;
|
if(!StandaloneOwnerLoader::Resolve(OwnerJsonRoot,*World,Error))return false;
|
||||||
if(!bParityChecked&&Seed==World->Seed&&!bRoundTunnels)PrintParity();
|
if(!bParityChecked&&Seed==World->Seed&&!bRoundTunnels)PrintParity();
|
||||||
const int32 ExportClipRadius=World->ClipRadius;
|
const int32 ExportClipRadius=World->ClipRadius;
|
||||||
World->ClipRadius=ClipRadius;
|
World->ClipRadius=ClipRadius;
|
||||||
@@ -722,8 +724,10 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
Min.X-=NearBuildPad;Min.Y-=NearBuildPad;Min.Z-=NearBuildPad;
|
Min.X-=NearBuildPad;Min.Y-=NearBuildPad;Min.Z-=NearBuildPad;
|
||||||
Max.X+=NearBuildPad;Max.Y+=NearBuildPad;Max.Z+=NearBuildPad;
|
Max.X+=NearBuildPad;Max.Y+=NearBuildPad;Max.Z+=NearBuildPad;
|
||||||
}
|
}
|
||||||
Snapshot=VoxelForgeCore::MakeStandaloneSnapshot(World);
|
Snapshot=VoxelForgeCore::MakeStandaloneSnapshotFromParsedJson(OwnerJsonRoot,Seed,ClipRadius,
|
||||||
if(!Snapshot){Error="the standalone adapter could not make a resolved core snapshot";return false;}
|
MaxClipLevelOverride,bRoundTunnels,bRoundTunnelEnhanced,Error);
|
||||||
|
if(!Snapshot)
|
||||||
|
{if(Error.empty())Error="the standalone adapter could not make a resolved core snapshot";return false;}
|
||||||
ShapeWorld=std::make_unique<VoxelForgeCore::World>(Snapshot);
|
ShapeWorld=std::make_unique<VoxelForgeCore::World>(Snapshot);
|
||||||
const int32 ShapeWorkers=bNeighborhoodOnly?(WorkerCount>=8?2:1):WorkerCount;
|
const int32 ShapeWorkers=bNeighborhoodOnly?(WorkerCount>=8?2:1):WorkerCount;
|
||||||
VoxelForgeCore::ShapeBuildRequest Request;
|
VoxelForgeCore::ShapeBuildRequest Request;
|
||||||
@@ -1941,6 +1945,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
}
|
}
|
||||||
|
|
||||||
int RunPipelinedStreamingBenchmark(std::shared_ptr<FStandaloneOwnerWorld> OwnerWorld,
|
int RunPipelinedStreamingBenchmark(std::shared_ptr<FStandaloneOwnerWorld> OwnerWorld,
|
||||||
|
const VoxelForgeCore::ResolvedWorldSnapshot& CoreSnapshot,
|
||||||
const FVector& CameraPositionVoxels,int32 WorkerCount,int32 MaxLevel,
|
const FVector& CameraPositionVoxels,int32 WorkerCount,int32 MaxLevel,
|
||||||
VoxelForgeCore::DetailVariant DetailVariant,const VoxelForgeCore::JobScheduler& Scheduler,
|
VoxelForgeCore::DetailVariant DetailVariant,const VoxelForgeCore::JobScheduler& Scheduler,
|
||||||
bool bHashDensity,bool bSkipUvAndColors,bool bSkipMaterialGroups,bool bSkipUV,bool bSkipColors,
|
bool bHashDensity,bool bSkipUvAndColors,bool bSkipMaterialGroups,bool bSkipUV,bool bSkipColors,
|
||||||
@@ -1948,7 +1953,15 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
{
|
{
|
||||||
using Clock=std::chrono::steady_clock;
|
using Clock=std::chrono::steady_clock;
|
||||||
FStandaloneOwnerWorld& World=*OwnerWorld;
|
FStandaloneOwnerWorld& World=*OwnerWorld;
|
||||||
|
const bool bRoundTunnels=World.Generator->bStandaloneResearchRoundTunnels;
|
||||||
|
const bool bEnhancedTunnelWalls=World.Generator->bStandaloneResearchEnhancedWalls;
|
||||||
|
VoxelForgeCore::World Shape(CoreSnapshot);
|
||||||
FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,CameraPositionVoxels);
|
FStandaloneDesiredTileSet Desired=StandaloneWorldMesher::BuildDesiredSet(World,CameraPositionVoxels);
|
||||||
|
const auto CoreTiles=Shape.desiredTiles(
|
||||||
|
{CameraPositionVoxels.X,CameraPositionVoxels.Y,CameraPositionVoxels.Z});
|
||||||
|
Desired.Tiles.clear();Desired.Tiles.reserve(CoreTiles.size());
|
||||||
|
for(const auto& Key:CoreTiles)
|
||||||
|
Desired.Tiles.emplace_back(FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
|
||||||
if(MaxLevel>=0)
|
if(MaxLevel>=0)
|
||||||
Desired.Tiles.erase(std::remove_if(Desired.Tiles.begin(),Desired.Tiles.end(),
|
Desired.Tiles.erase(std::remove_if(Desired.Tiles.begin(),Desired.Tiles.end(),
|
||||||
[&](const FVoxelTileKey& Tile){return Tile.Level>MaxLevel;}),Desired.Tiles.end());
|
[&](const FVoxelTileKey& Tile){return Tile.Level>MaxLevel;}),Desired.Tiles.end());
|
||||||
@@ -1978,7 +1991,15 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
if(Neighborhood.size()!=27)
|
if(Neighborhood.size()!=27)
|
||||||
{std::fprintf(stderr,"[StreamPipeline] expected 27 player-neighborhood tiles, found %zu.\n",Neighborhood.size());return 7;}
|
{std::fprintf(stderr,"[StreamPipeline] expected 27 player-neighborhood tiles, found %zu.\n",Neighborhood.size());return 7;}
|
||||||
|
|
||||||
VoxelForgeCore::World Shape(VoxelForgeCore::MakeStandaloneSnapshot(OwnerWorld));
|
// The initial desired set now owns all clipmap metadata needed by this Core-only run.
|
||||||
|
// Release the separate compatibility-world generation graph before compiling Core.
|
||||||
|
World.Generator.reset();
|
||||||
|
World.Manager.reset();
|
||||||
|
World.DefinitionStorage.clear();
|
||||||
|
World.BiomeStorage.clear();
|
||||||
|
World.OperationStorage.clear();
|
||||||
|
World.DensitySamples.clear();
|
||||||
|
|
||||||
std::string BuildError;
|
std::string BuildError;
|
||||||
const bool bIncludeFarWorld=MaxLevel<0||MaxLevel>=World.FullResClipLevels;
|
const bool bIncludeFarWorld=MaxLevel<0||MaxLevel>=World.FullResClipLevels;
|
||||||
const auto ShapeBuildStart=Clock::now();
|
const auto ShapeBuildStart=Clock::now();
|
||||||
@@ -1989,8 +2010,8 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
ShapeRequest.clipMaxVoxel={ClipMax.X,ClipMax.Y,ClipMax.Z};
|
ShapeRequest.clipMaxVoxel={ClipMax.X,ClipMax.Y,ClipMax.Z};
|
||||||
ShapeRequest.workerLimit=static_cast<uint32>(std::clamp(WorkerCount,1,8));
|
ShapeRequest.workerLimit=static_cast<uint32>(std::clamp(WorkerCount,1,8));
|
||||||
ShapeRequest.detail=DetailVariant;ShapeRequest.includeFarWorld=bIncludeFarWorld;
|
ShapeRequest.detail=DetailVariant;ShapeRequest.includeFarWorld=bIncludeFarWorld;
|
||||||
ShapeRequest.roundTunnels=World.Generator->bStandaloneResearchRoundTunnels;
|
ShapeRequest.roundTunnels=bRoundTunnels;
|
||||||
ShapeRequest.enhancedTunnelWalls=World.Generator->bStandaloneResearchEnhancedWalls;
|
ShapeRequest.enhancedTunnelWalls=bEnhancedTunnelWalls;
|
||||||
if(!Shape.compile(ShapeRequest,Scheduler,BuildError))
|
if(!Shape.compile(ShapeRequest,Scheduler,BuildError))
|
||||||
{std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2;}
|
{std::fprintf(stderr,"[StreamPipeline] full shape build failed: %s\n",BuildError.c_str());return 2;}
|
||||||
const auto ShapeReady=Clock::now();
|
const auto ShapeReady=Clock::now();
|
||||||
@@ -1998,7 +2019,7 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
|
FStandaloneStreamingGenerator Streamer(World,WorkerCount,bHashDensity,nullptr,MaxLevel,
|
||||||
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape);
|
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape);
|
||||||
const auto StreamStart=Clock::now();
|
const auto StreamStart=Clock::now();
|
||||||
const FStandaloneStreamingUpdate Update=Streamer.Start(CameraPositionVoxels);
|
const FStandaloneStreamingUpdate Update=Streamer.StartWithDesired(Desired);
|
||||||
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
|
if(Update.DesiredTiles!=static_cast<int32>(Desired.Tiles.size()))
|
||||||
{
|
{
|
||||||
Streamer.Stop();
|
Streamer.Stop();
|
||||||
@@ -2153,11 +2174,23 @@ float3 Lit=Rock*(Ambient+0.88*Diffuse)+float3(0.50,0.69,0.82)*Head*0.75;
|
|||||||
auto PlayerNeighborhoodCompleted=std::chrono::steady_clock::time_point{};
|
auto PlayerNeighborhoodCompleted=std::chrono::steady_clock::time_point{};
|
||||||
std::vector<FStandaloneCpuTile> CacheTiles;
|
std::vector<FStandaloneCpuTile> CacheTiles;
|
||||||
if(!BakeCachePath.empty())CacheTiles.reserve(Desired.Tiles.size());
|
if(!BakeCachePath.empty())CacheTiles.reserve(Desired.Tiles.size());
|
||||||
|
if(CoreWorld&&!FarAudit)
|
||||||
|
{
|
||||||
|
// Tile keys and band limits are already captured in Desired. Core owns field and
|
||||||
|
// mesh generation, so discard the unused compatibility generator before streaming.
|
||||||
|
World.Generator.reset();
|
||||||
|
World.Manager.reset();
|
||||||
|
World.DefinitionStorage.clear();
|
||||||
|
World.BiomeStorage.clear();
|
||||||
|
World.OperationStorage.clear();
|
||||||
|
World.DensitySamples.clear();
|
||||||
|
}
|
||||||
const auto Start=std::chrono::steady_clock::now();
|
const auto Start=std::chrono::steady_clock::now();
|
||||||
FStandaloneStreamingGenerator Streamer(World,WorkerCount,
|
FStandaloneStreamingGenerator Streamer(World,WorkerCount,
|
||||||
bHashDensity||!BakeCachePath.empty(),Cache,MaxLevel,
|
bHashDensity||!BakeCachePath.empty(),Cache,MaxLevel,
|
||||||
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,CoreWorld);
|
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,CoreWorld);
|
||||||
const FStandaloneStreamingUpdate Update=Streamer.Start(CameraPositionVoxels);
|
const FStandaloneStreamingUpdate Update=CoreWorld
|
||||||
|
?Streamer.StartWithDesired(Desired):Streamer.Start(CameraPositionVoxels);
|
||||||
const double LOD0Width=static_cast<double>(2*World.ClipRadius+2)
|
const double LOD0Width=static_cast<double>(2*World.ClipRadius+2)
|
||||||
*CHUNK_SIZE*VOXEL_SIZE/100.0;
|
*CHUNK_SIZE*VOXEL_SIZE/100.0;
|
||||||
std::printf("[StreamBenchmarkConfig] clip_radius=%d lod0_cube=%.0fm voxel=25cm fullres_levels=%d max_level_filter=%d workers=%d full_detail_tiles=%d desired_tiles=%d variant=%s uv=%s colors=%s material_groups=%s hash=%s\n",
|
std::printf("[StreamBenchmarkConfig] clip_radius=%d lod0_cube=%.0fm voxel=25cm fullres_levels=%d max_level_filter=%d workers=%d full_detail_tiles=%d desired_tiles=%d variant=%s uv=%s colors=%s material_groups=%s hash=%s\n",
|
||||||
@@ -2564,7 +2597,17 @@ int main(int Argc,char** Argv)
|
|||||||
App.bProfileDetailBand=bProfileDetailBand;
|
App.bProfileDetailBand=bProfileDetailBand;
|
||||||
std::string Error;
|
std::string Error;
|
||||||
App.World=std::make_shared<FStandaloneOwnerWorld>();
|
App.World=std::make_shared<FStandaloneOwnerWorld>();
|
||||||
if(!StandaloneOwnerLoader::Load(App.DataPath,*App.World,Error)){std::fprintf(stderr,"Owner data load failed: %s\n",Error.c_str());return 2;}
|
{
|
||||||
|
std::ifstream JsonFile(App.DataPath,std::ios::binary);
|
||||||
|
if(!JsonFile){std::fprintf(stderr,"Owner data load failed: cannot open %s\n",App.DataPath.c_str());return 2;}
|
||||||
|
std::string OwnerJsonText{std::istreambuf_iterator<char>(JsonFile),std::istreambuf_iterator<char>{}};
|
||||||
|
if(!JsonFile&& !JsonFile.eof())
|
||||||
|
{std::fprintf(stderr,"Owner data load failed: read error for %s\n",App.DataPath.c_str());return 2;}
|
||||||
|
if(!VFJson::Parser(std::move(OwnerJsonText)).Parse(App.OwnerJsonRoot,Error))
|
||||||
|
{std::fprintf(stderr,"Owner data load failed: %s\n",Error.c_str());return 2;}
|
||||||
|
}
|
||||||
|
if(!StandaloneOwnerLoader::Resolve(App.OwnerJsonRoot,*App.World,Error))
|
||||||
|
{std::fprintf(stderr,"Owner data load failed: %s\n",Error.c_str());return 2;}
|
||||||
App.Seed=App.World->Seed;
|
App.Seed=App.World->Seed;
|
||||||
if(App.bParityOnly)
|
if(App.bParityOnly)
|
||||||
{
|
{
|
||||||
@@ -2597,6 +2640,14 @@ int main(int Argc,char** Argv)
|
|||||||
&&!bStreamPipeline
|
&&!bStreamPipeline
|
||||||
&&!App.BuildShapeWorldForCamera(Error,bNeedFarShapeWorld))
|
&&!App.BuildShapeWorldForCamera(Error,bNeedFarShapeWorld))
|
||||||
{std::fprintf(stderr,"Shape interval build failed: %s\n",Error.c_str());return 2;}
|
{std::fprintf(stderr,"Shape interval build failed: %s\n",Error.c_str());return 2;}
|
||||||
|
if(bStreamPipeline)
|
||||||
|
{
|
||||||
|
App.Snapshot=VoxelForgeCore::MakeStandaloneSnapshotFromParsedJson(App.OwnerJsonRoot,
|
||||||
|
App.Seed,App.ClipRadius,App.MaxClipLevelOverride,App.bRoundTunnels,
|
||||||
|
App.bRoundTunnelEnhanced,Error);
|
||||||
|
if(!App.Snapshot)
|
||||||
|
{std::fprintf(stderr,"Shape interval build failed: %s\n",Error.c_str());return 2;}
|
||||||
|
}
|
||||||
if(App.bShapeAuditOnly)
|
if(App.bShapeAuditOnly)
|
||||||
{
|
{
|
||||||
if(bPassageDetailAuditOnly)
|
if(bPassageDetailAuditOnly)
|
||||||
@@ -2614,7 +2665,7 @@ int main(int Argc,char** Argv)
|
|||||||
if(bStreamBenchmark)
|
if(bStreamBenchmark)
|
||||||
{
|
{
|
||||||
if(bStreamPipeline)
|
if(bStreamPipeline)
|
||||||
return RunPipelinedStreamingBenchmark(App.World,
|
return RunPipelinedStreamingBenchmark(App.World,App.Snapshot,
|
||||||
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
|
FVector(App.Position.X*4.0,App.Position.Y*4.0,App.Position.Z*4.0),
|
||||||
App.WorkerCount,StreamBenchmarkMaxLevel,App.ShapeDetailVariant,App.CoreScheduler,
|
App.WorkerCount,StreamBenchmarkMaxLevel,App.ShapeDetailVariant,App.CoreScheduler,
|
||||||
bStreamHash||!BakeMeshCachePath.empty(),bSkipUvAndColors,bSkipMaterialGroups,
|
bStreamHash||!BakeMeshCachePath.empty(),bSkipUvAndColors,bSkipMaterialGroups,
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ struct FStandaloneCpuTile
|
|||||||
bool bHasTopologyHash=false;
|
bool bHasTopologyHash=false;
|
||||||
uint64 MeshPayloadAllocatedBytes=0;
|
uint64 MeshPayloadAllocatedBytes=0;
|
||||||
std::array<uint64,VoxelDensityProfile::BucketCount> ProfileCycles{};
|
std::array<uint64,VoxelDensityProfile::BucketCount> ProfileCycles{};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
inline uint64 HashStandaloneMeshPayload(const FVoxelMeshData& Mesh)
|
inline uint64 HashStandaloneMeshPayload(const FVoxelMeshData& Mesh)
|
||||||
@@ -428,6 +429,19 @@ public:
|
|||||||
return UpdateDesired(CameraPositionVoxels,ResidentKeys,CrossingIndex);
|
return UpdateDesired(CameraPositionVoxels,ResidentKeys,CrossingIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FStandaloneStreamingUpdate StartWithDesired(const FStandaloneDesiredTileSet& InitialDesired,
|
||||||
|
const std::vector<FVoxelTileKey>& ResidentKeys={},
|
||||||
|
uint64 CrossingIndex=0)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> Lock(Mutex);
|
||||||
|
bStop=false;
|
||||||
|
}
|
||||||
|
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this](){WorkerLoop();});
|
||||||
|
return UpdateDesiredSet(InitialDesired,ResidentKeys,CrossingIndex);
|
||||||
|
}
|
||||||
|
|
||||||
void SetDesiredTileFilter(const std::vector<FVoxelTileKey>& Keys)
|
void SetDesiredTileFilter(const std::vector<FVoxelTileKey>& Keys)
|
||||||
{
|
{
|
||||||
TileFilter.clear();TileFilter.reserve(Keys.size()*2+1);
|
TileFilter.clear();TileFilter.reserve(Keys.size()*2+1);
|
||||||
@@ -461,6 +475,33 @@ public:
|
|||||||
for(const auto& Key:CoreTiles)
|
for(const auto& Key:CoreTiles)
|
||||||
NewDesired.Tiles.emplace_back(FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
|
NewDesired.Tiles.emplace_back(FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
|
||||||
}
|
}
|
||||||
|
return UpdateDesiredSet(std::move(NewDesired),ResidentKeys,CrossingIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<FStandaloneCpuTile> TakeReady()
|
||||||
|
{
|
||||||
|
std::vector<FStandaloneCpuTile> Out;
|
||||||
|
std::lock_guard<std::mutex> Lock(Mutex);
|
||||||
|
Out.swap(Ready);
|
||||||
|
for(const FStandaloneCpuTile& Tile:Out)Outstanding.erase(Tile.Key);
|
||||||
|
return Out;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDesired(const FVoxelTileKey& Key)const
|
||||||
|
{std::lock_guard<std::mutex> Lock(Mutex);return Desired.contains(Key);}
|
||||||
|
int32 DesiredCount()const
|
||||||
|
{std::lock_guard<std::mutex> Lock(Mutex);return static_cast<int32>(Desired.size());}
|
||||||
|
int32 FullResDesiredCount()const
|
||||||
|
{std::lock_guard<std::mutex> Lock(Mutex);return FullResTileCount;}
|
||||||
|
FIntVector CenterChunk()const
|
||||||
|
{std::lock_guard<std::mutex> Lock(Mutex);return CurrentDesired.CenterChunk;}
|
||||||
|
int32 GetWorkerCount()const{return WorkerCount;}
|
||||||
|
uint64 GetCacheHits()const{return CacheHitCount.load(std::memory_order_relaxed);}
|
||||||
|
|
||||||
|
private:
|
||||||
|
FStandaloneStreamingUpdate UpdateDesiredSet(FStandaloneDesiredTileSet NewDesired,
|
||||||
|
const std::vector<FVoxelTileKey>& ResidentKeys,uint64 CrossingIndex)
|
||||||
|
{
|
||||||
if(MaxLevelFilter>=0)
|
if(MaxLevelFilter>=0)
|
||||||
NewDesired.Tiles.erase(std::remove_if(NewDesired.Tiles.begin(),NewDesired.Tiles.end(),
|
NewDesired.Tiles.erase(std::remove_if(NewDesired.Tiles.begin(),NewDesired.Tiles.end(),
|
||||||
[&](const FVoxelTileKey& Key){return Key.Level>MaxLevelFilter;}),NewDesired.Tiles.end());
|
[&](const FVoxelTileKey& Key){return Key.Level>MaxLevelFilter;}),NewDesired.Tiles.end());
|
||||||
@@ -523,28 +564,6 @@ public:
|
|||||||
Wake.notify_all();
|
Wake.notify_all();
|
||||||
return Stats;
|
return Stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<FStandaloneCpuTile> TakeReady()
|
|
||||||
{
|
|
||||||
std::vector<FStandaloneCpuTile> Out;
|
|
||||||
std::lock_guard<std::mutex> Lock(Mutex);
|
|
||||||
Out.swap(Ready);
|
|
||||||
for(const FStandaloneCpuTile& Tile:Out)Outstanding.erase(Tile.Key);
|
|
||||||
return Out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsDesired(const FVoxelTileKey& Key)const
|
|
||||||
{std::lock_guard<std::mutex> Lock(Mutex);return Desired.contains(Key);}
|
|
||||||
int32 DesiredCount()const
|
|
||||||
{std::lock_guard<std::mutex> Lock(Mutex);return static_cast<int32>(Desired.size());}
|
|
||||||
int32 FullResDesiredCount()const
|
|
||||||
{std::lock_guard<std::mutex> Lock(Mutex);return FullResTileCount;}
|
|
||||||
FIntVector CenterChunk()const
|
|
||||||
{std::lock_guard<std::mutex> Lock(Mutex);return CurrentDesired.CenterChunk;}
|
|
||||||
int32 GetWorkerCount()const{return WorkerCount;}
|
|
||||||
uint64 GetCacheHits()const{return CacheHitCount.load(std::memory_order_relaxed);}
|
|
||||||
|
|
||||||
private:
|
|
||||||
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job)
|
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job)
|
||||||
{
|
{
|
||||||
const auto StartTime=FClock::now();
|
const auto StartTime=FClock::now();
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ struct FVoxelTileKey
|
|||||||
|
|
||||||
FORCEINLINE uint32 GetTypeHash(const FVoxelTileKey& K)
|
FORCEINLINE uint32 GetTypeHash(const FVoxelTileKey& K)
|
||||||
{
|
{
|
||||||
return HashCombine(GetTypeHash(K.Coord), ::GetTypeHash(K.Level));
|
return HashCombine(GetTypeHash(K.Coord), GetTypeHash(K.Level));
|
||||||
}
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|||||||
Reference in new issue
Block a user