Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Source.Common/DtCommon.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public struct ExcludeProp
public struct BuildHierarchyStruct
{
public ExcludeProp[]? ExcludeProps;
public int NumExcludeProps;

public InlineArrayMaxTotalSendTableProps<SendProp> DataTableProps;
public int NumDataTableProps;
Expand Down
2 changes: 1 addition & 1 deletion Source.Common/DtRecv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ public bool SetupFlatPropertyArray() {
// Now build the hierarchy.
BuildHierarchyStruct bhs = default;
bhs.ExcludeProps = excludeProps;
bhs.ExcludeProps = excludeProps;
bhs.NumExcludeProps = numExcludeProps;
bhs.NumProps = bhs.NumDataTableProps = 0;
bhs.PropProxies = 0;
SendTable.BuildHierarchy(GetRootNode(), table, ref bhs);
Expand Down
7 changes: 3 additions & 4 deletions Source.Common/DtSend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -807,9 +807,8 @@ private static void BuildHierarchy_IterateProps(SendNode node, SendTable? table,
for (i = 0; i < table?.Props?.Length; i++) {
SendProp prop = table.Props![i];

if (prop.IsExcludeProp() || prop.IsInsideArray() || FindExcludeProp(table.GetName(), prop.GetName(), bhs.ExcludeProps!)) {
if (prop.IsExcludeProp() || prop.IsInsideArray() || FindExcludeProp(table.GetName(), prop.GetName(), bhs.ExcludeProps!.AsSpan()[..bhs.NumExcludeProps]))
continue;
}

if (prop.GetPropType() == SendPropType.DataTable) {
if ((prop.GetFlags() & PropFlags.Collapsible) != 0) {
Expand Down Expand Up @@ -844,8 +843,8 @@ private static void BuildHierarchy_IterateProps(SendNode node, SendTable? table,
}
}

private static bool FindExcludeProp(ReadOnlySpan<char> tableName, ReadOnlySpan<char> propName, ExcludeProp[]? excludeProps) {
for (int i = 0; i < excludeProps?.Length; i++) {
private static bool FindExcludeProp(ReadOnlySpan<char> tableName, ReadOnlySpan<char> propName, Span<ExcludeProp> excludeProps) {
for (int i = 0, c = excludeProps.Length; i < c; i++) {
if (tableName.Equals(excludeProps[i].TableName!, StringComparison.OrdinalIgnoreCase) && propName.Equals(excludeProps[i].PropName!, StringComparison.OrdinalIgnoreCase)) {
return true;
}
Expand Down
107 changes: 78 additions & 29 deletions Source.Filesystem/PackStoreSearchPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,86 @@ public void Dispose() {

public class PackStoreSearchPath : BaseSearchPath
{
sealed class PackStore
{
public readonly string Key;
public readonly VpkArchive Vpk = new();
public readonly Dictionary<UtlSymId_t, VpkEntry> EntryLookups = [];
public readonly Dictionary<UtlSymId_t, VpkDirectory> DirectoryLookups = [];
public int References;

public PackStore(string key, string vpkPath) {
Key = key;
Vpk.Load(vpkPath);

int entryCount = 0;
foreach (var dir in Vpk.Directories)
entryCount += dir.Entries.Count;

DirectoryLookups.EnsureCapacity(Vpk.Directories.Count);
EntryLookups.EnsureCapacity(entryCount);

Span<char> buildPath = stackalloc char[260];
foreach (var dir in Vpk.Directories) {
DirectoryLookups[dir.Path.Hash()] = dir;

foreach (var entry in dir.Entries) {
var path = entry.Path.Replace('\\', '/');
var filename = entry.Filename;
var ext = entry.Extension;

int strlen = 0;
if (path.Length > 0 && path[0] != ' ') {
path.CopyTo(buildPath[strlen..]); strlen += path.Length;
buildPath[strlen] = '/'; strlen += 1;
}
filename.CopyTo(buildPath[strlen..]); strlen += filename.Length;
buildPath[strlen] = '.'; strlen += 1;
ext.CopyTo(buildPath[strlen..]); strlen += ext.Length;

ReadOnlySpan<char> finalSpan = buildPath[..strlen];
EntryLookups[finalSpan.Hash()] = entry;
}
}
}
}

static readonly Dictionary<string, PackStore> packStores = new(OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal);

static PackStore AcquirePackStore(string vpkPath) {
string key = Path.GetFullPath(vpkPath);
lock (packStores) {
if (!packStores.TryGetValue(key, out PackStore? store)) {
store = new(key, vpkPath);
packStores[key] = store;
}

store.References++;
return store;
}
}

static void ReleasePackStore(PackStore store) {
lock (packStores) {
if (--store.References > 0)
return;

packStores.Remove(store.Key);
}

store.Vpk.Dispose();
}

private readonly IFileSystem parent;
private readonly VpkArchive vpk;
private readonly PackStore? store;
private readonly string VpkPath;

private Dictionary<UtlSymId_t, VpkEntry> vpkEntryLookups = [];
private Dictionary<UtlSymId_t, VpkDirectory> vpkDirectoryLookups = [];
private readonly Dictionary<UtlSymId_t, VpkEntry> vpkEntryLookups;
private readonly Dictionary<UtlSymId_t, VpkDirectory> vpkDirectoryLookups;

~PackStoreSearchPath(){
vpk.Dispose();
if (store != null)
ReleasePackStore(store);
}

public PackStoreSearchPath(IFileSystem filesystem, string absPath) {
Expand All @@ -46,31 +117,9 @@ public PackStoreSearchPath(IFileSystem filesystem, string absPath) {
absPath = $"{absPath}_dir.vpk";
VpkPath = absPath;
parent = filesystem;
vpk = new VpkArchive();
vpk.Load(absPath);

Span<char> buildPath = stackalloc char[260];
foreach (var dir in vpk.Directories) {
vpkDirectoryLookups[dir.Path.Hash()] = dir;

foreach (var entry in dir.Entries) {
var path = entry.Path.Replace('\\', '/');
var filename = entry.Filename;
var ext = entry.Extension;

int strlen = 0;
if (path.Length > 0 && path[0] != ' ') {
path.CopyTo(buildPath[strlen..]); strlen += path.Length;
buildPath[strlen] = '/'; strlen += 1;
}
filename.CopyTo(buildPath[strlen..]); strlen += filename.Length;
buildPath[strlen] = '.'; strlen += 1;
ext.CopyTo(buildPath[strlen..]); strlen += ext.Length;

ReadOnlySpan<char> finalSpan = buildPath[..strlen];
vpkEntryLookups[finalSpan.Hash()] = entry;
}
}
store = AcquirePackStore(absPath);
vpkEntryLookups = store.EntryLookups;
vpkDirectoryLookups = store.DirectoryLookups;

if (!Path.IsPathFullyQualified(absPath))
absPath = Path.GetFullPath(absPath);
Expand Down
16 changes: 10 additions & 6 deletions Source.SoundEmitterSystem/SoundEmitterSystemBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@ public class SoundEmitterSystemBase : ISoundEmitterSystemBase
readonly Dictionary<UtlSymId_t, Gender> m_ActorGenders = new(SymbolStringComparer_OrdinalIgnoreCase.Instance);

readonly LinkedList<SoundEntry> Sounds = new();
readonly Dictionary<SoundEntry, LinkedListNode<SoundEntry>> SoundEntryLookup = new();
readonly Dictionary<int, LinkedListNode<SoundEntry>> HandleToSound = [];
readonly Dictionary<LinkedListNode<SoundEntry>, int> SoundToHandle = [];

int CurrentHandle;
int Sounds_AllocHandle(LinkedListNode<SoundEntry> node) {
Sounds.AddLast(node);
SoundEntryLookup[node.Value] = node;
int handle = Interlocked.Increment(ref CurrentHandle);
HandleToSound[handle] = node;
SoundToHandle[node] = handle;
Expand All @@ -94,6 +96,8 @@ void Sounds_ReplaceKey(int handle, LinkedListNode<SoundEntry> newNode) {
Sounds.AddAfter(oldNode, newNode);
Sounds.Remove(oldNode);
SoundToHandle.Remove(oldNode);

SoundEntryLookup[newNode.Value] = newNode;
HandleToSound[handle] = newNode;
SoundToHandle[newNode] = handle;
}
Expand Down Expand Up @@ -143,8 +147,7 @@ public bool AddSound(ReadOnlySpan<char> soundname, ReadOnlySpan<char> scriptfile
newEntry.ScriptFileIndex = i;
newEntry.SoundParams.CopyFrom(parms);

LinkedListNode<SoundEntry>? tryFindingDuplicate = Sounds.Find(newEntry);
if (tryFindingDuplicate != null)
if (SoundEntryLookup.TryGetValue(newEntry, out LinkedListNode<SoundEntry>? tryFindingDuplicate))
Sounds_ReplaceKey(SoundToHandle[tryFindingDuplicate], entryNode);
else
Sounds_AllocHandle(entryNode);
Expand Down Expand Up @@ -495,8 +498,8 @@ public int GetSoundIndex(ReadOnlySpan<char> name) {

SoundEntry search = default;
search.Name = name;
var node = Sounds.Find(search);
if (node == null) return -1;
if (!SoundEntryLookup.TryGetValue(search, out var node))
return -1;
return SoundToHandle[node];
}

Expand Down Expand Up @@ -728,8 +731,7 @@ public void AddSoundsFromFile(ReadOnlySpan<char> filename, bool preload, bool is
bool isDuplicate;
int lookup = -1;
{
var lookupEntry = Sounds.Find(entry);
if (lookupEntry != null) {
if (SoundEntryLookup.TryGetValue(entry, out var lookupEntry)) {
isDuplicate = true;
lookup = SoundToHandle[lookupEntry];
}
Expand Down Expand Up @@ -870,6 +872,7 @@ private void InternalModShutdown() {
SoundKeyValues.Clear();

Sounds.Clear();
SoundEntryLookup.Clear();
HandleToSound.Clear();
CurrentHandle = 0;

Expand Down Expand Up @@ -962,6 +965,7 @@ public void RenameSound(ReadOnlySpan<char> soundname, ReadOnlySpan<char> newname
entry.Name = newname;
// Re-insert in new spot
Sounds.AddLast(entryNode);
SoundEntryLookup[entry] = entryNode;

// Mark associated script as dirty
SoundKeyValues.AsSpan()[entry.ScriptFileIndex].Dirty = true;
Expand Down
141 changes: 78 additions & 63 deletions Source.VPK/VpkArchive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ public VpkArchive() {
}

public void Dispose() {
foreach (var part in Parts)
part.Dispose();
FileHandle.Dispose();
if (Parts != null)
foreach (var part in Parts)
part.Dispose();
FileHandle?.Dispose();
}

public void Load(string filename, VpkVersions.Versions version = VpkVersions.Versions.Any) {
Expand All @@ -32,75 +33,89 @@ public void Load(string filename, VpkVersions.Versions version = VpkVersions.Ver
if (IsMultiPart)
LoadParts(filename);

switch (version) {
case VpkVersions.Versions.Any:
// Try V2 first
_reader = new V2.VpkReaderV2(filename);
var hdr_anyv2 = _reader.ReadArchiveHeader();
if (!hdr_anyv2.Verify()) {
// Try V1
IVpkArchiveHeader header = null!;
try {
switch (version) {
case VpkVersions.Versions.Any:
// Try V2 first
_reader = new V2.VpkReaderV2(filename);
header = _reader.ReadArchiveHeader();
if (!header.Verify()) {
// Try V1
_reader.Dispose();
_reader = new VpkReaderV1(filename);

header = _reader.ReadArchiveHeader();
if (!header.Verify())
throw new ArchiveParsingException("Invalid archive header (tried V2, then V1)");
}
break;
case VpkVersions.Versions.V1:
_reader = new VpkReaderV1(filename);

var hdr_anyv1 = _reader.ReadArchiveHeader();
if (!hdr_anyv1.Verify())
throw new ArchiveParsingException("Invalid archive header (tried V2, then V1)");
}
break;
case VpkVersions.Versions.V1:
_reader = new VpkReaderV1(filename);

var hdr_v1 = _reader.ReadArchiveHeader();
if (!hdr_v1.Verify())
throw new ArchiveParsingException("Invalid V1 archive header");
break;
case VpkVersions.Versions.V2:
_reader = new V2.VpkReaderV2(filename);

var hdr_v2 = _reader.ReadArchiveHeader();
if (!hdr_v2.Verify())
throw new ArchiveParsingException("Invalid V2 archive header");
break;
}
header = _reader.ReadArchiveHeader();
if (!header.Verify())
throw new ArchiveParsingException("Invalid V1 archive header");
break;
case VpkVersions.Versions.V2:
_reader = new V2.VpkReaderV2(filename);

header = _reader.ReadArchiveHeader();
if (!header.Verify())
throw new ArchiveParsingException("Invalid V2 archive header");
break;
}

var reader = _reader.ReadDirectories(this);
while (reader.MoveNext())
Directories.Add(reader.Current);
var reader = _reader.ReadDirectories(this, header.TreeLength);
while (reader.MoveNext())
Directories.Add(reader.Current);
}
finally {
_reader?.Dispose();
}
}

public void Load(byte[] file, VpkVersions.Versions version = VpkVersions.Versions.V1) {
switch (version) {
case VpkVersions.Versions.Any:
// Try V2 first
_reader = new V2.VpkReaderV2(file);
var hdr_anyv2 = _reader.ReadArchiveHeader();
if (!hdr_anyv2.Verify()) {
// Try V1
IVpkArchiveHeader header = null!;
try {
switch (version) {
case VpkVersions.Versions.Any:
// Try V2 first
_reader = new V2.VpkReaderV2(file);
header = _reader.ReadArchiveHeader();
if (!header.Verify()) {
// Try V1
_reader.Dispose();
_reader = new VpkReaderV1(file);

header = _reader.ReadArchiveHeader();
if (!header.Verify())
throw new ArchiveParsingException("Invalid archive header (tried V2, then V1)");
}
break;
case VpkVersions.Versions.V1:
_reader = new VpkReaderV1(file);

var hdr_anyv1 = _reader.ReadArchiveHeader();
if (!hdr_anyv1.Verify())
throw new ArchiveParsingException("Invalid archive header (tried V2, then V1)");
}
break;
case VpkVersions.Versions.V1:
_reader = new VpkReaderV1(file);

var hdr_v1 = _reader.ReadArchiveHeader();
if (!hdr_v1.Verify())
throw new ArchiveParsingException("Invalid V1 archive header");
break;
case VpkVersions.Versions.V2:
_reader = new V2.VpkReaderV2(file);

var hdr_v2 = _reader.ReadArchiveHeader();
if (!hdr_v2.Verify())
throw new ArchiveParsingException("Invalid V2 archive header");
break;
}
header = _reader.ReadArchiveHeader();
if (!header.Verify())
throw new ArchiveParsingException("Invalid V1 archive header");
break;
case VpkVersions.Versions.V2:
_reader = new V2.VpkReaderV2(file);

header = _reader.ReadArchiveHeader();
if (!header.Verify())
throw new ArchiveParsingException("Invalid V2 archive header");
break;
}

var reader = _reader.ReadDirectories(this);
while (reader.MoveNext())
Directories.Add(reader.Current);
var reader = _reader.ReadDirectories(this, header.TreeLength);
while (reader.MoveNext())
Directories.Add(reader.Current);
}
finally {
_reader?.Dispose();
}
}

private void LoadParts(string filePath) {
Expand Down
Loading
Loading