Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

Commit

Permalink
⚡️ Improve performance of CustomName and CustomNameInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
Chasmical committed Aug 6, 2023
1 parent 6966ebf commit ac2151c
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 36 deletions.
30 changes: 17 additions & 13 deletions RogueLibsCore/Names/CustomName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,26 @@ namespace RogueLibsCore
/// <summary>
/// <para>The implementation of the <see cref="IName"/> interface, used by the <see cref="CustomNameProvider"/>. Provides properties for getting and setting the 8 default languages' localization texts.</para>
/// </summary>
public class CustomName : IName
public class CustomName : IName, IUsesNameContainer
{
internal CustomName(string name, string type, string english)
{
Name = name;
Type = type;
English = english;
container = english;
}
internal CustomName(string name, string type, IEnumerable<KeyValuePair<LanguageCode, string>> dictionary)
{
Name = name;
Type = type;
foreach (KeyValuePair<LanguageCode, string> pair in dictionary)
if (pair.Value is not null)
translations[pair.Key] = pair.Value;

if (dictionary is IUsesNameContainer nameCont)
container = NameContainer.Clone(nameCont.GetNameContainer());
else
{
foreach (KeyValuePair<LanguageCode, string> entry in dictionary)
container = NameContainer.Set(container, entry.Key, entry.Value);
}
}

/// <summary>
Expand Down Expand Up @@ -65,20 +70,19 @@ internal CustomName(string name, string type, IEnumerable<KeyValuePair<LanguageC
/// </summary>
public string? Korean { get => this[LanguageCode.Korean]; set => this[LanguageCode.Korean] = value; }

private readonly Dictionary<LanguageCode, string> translations = new Dictionary<LanguageCode, string>(1);
private object? container;
object? IUsesNameContainer.GetNameContainer() => container;

/// <inheritdoc/>
public string? this[LanguageCode language]
{
get => translations.TryGetValue(language, out string str) ? str : null;
set
{
if (value is not null) translations[language] = value;
else translations.Remove(language);
}
get => NameContainer.Get(container, language);
set => container = NameContainer.Set(container, language, value);
}

/// <inheritdoc/>
public IEnumerator<KeyValuePair<LanguageCode, string>> GetEnumerator() => translations.GetEnumerator();
public IEnumerator<KeyValuePair<LanguageCode, string>> GetEnumerator()
=> NameContainer.Enumerate(container);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
35 changes: 15 additions & 20 deletions RogueLibsCore/Names/CustomNameInfo.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace RogueLibsCore
{
/// <summary>
/// <para>The <see langword="struct"/> implementation of the <see cref="IName"/> interface. Provides properties for getting and setting the 8 default languages' localization texts.</para>
/// </summary>
public struct CustomNameInfo : IName
public struct CustomNameInfo : IName, IUsesNameContainer
{
/// <summary>
/// <para>Initializes a new instance of <see cref="CustomNameInfo"/> structure with the specified <paramref name="english"/> localization text.</para>
/// </summary>
/// <param name="english">The English localization text.</param>
public CustomNameInfo(string english)
{
if (english is null) throw new ArgumentNullException(nameof(english));
_texts = new Dictionary<LanguageCode, string>(1) { [LanguageCode.English] = english };
}
=> container = english ?? throw new ArgumentNullException(nameof(english));
/// <summary>
/// <para>Initializes a new instance of <see cref="CustomNameInfo"/> structure with localization texts from the specified <paramref name="dictionary"/>.</para>
/// </summary>
/// <param name="dictionary">The dictionary with localization texts.</param>
public CustomNameInfo(IEnumerable<KeyValuePair<LanguageCode, string>> dictionary)
{
if (dictionary is null) throw new ArgumentNullException(nameof(dictionary));
IEnumerable<KeyValuePair<LanguageCode, string>> pairs = dictionary.ToArray();
_texts = new Dictionary<LanguageCode, string>(pairs.Count());
foreach (KeyValuePair<LanguageCode, string> pair in pairs)
if (pair.Value != null)
_texts[pair.Key] = pair.Value;
if (dictionary is IUsesNameContainer name)
container = NameContainer.Clone(name.GetNameContainer());
else
{
foreach (KeyValuePair<LanguageCode, string> entry in dictionary)
container = NameContainer.Set(container, entry.Key, entry.Value);
}
}

/// <summary>
Expand Down Expand Up @@ -66,22 +64,19 @@ public CustomNameInfo(IEnumerable<KeyValuePair<LanguageCode, string>> dictionary
/// </summary>
public string? Korean { readonly get => this[LanguageCode.Korean]; set => this[LanguageCode.Korean] = value; }

private Dictionary<LanguageCode, string> _texts;
private Dictionary<LanguageCode, string> Texts => _texts ??= new Dictionary<LanguageCode, string>(1);
private object? container;
readonly object? IUsesNameContainer.GetNameContainer() => container;

/// <inheritdoc/>
public string? this[LanguageCode language]
{
readonly get => _texts != null && _texts.TryGetValue(language, out string str) ? str : null;
set
{
if (value != null) Texts[language] = value;
else _texts?.Remove(language);
}
readonly get => NameContainer.Get(container, language);
set => container = NameContainer.Set(container, language, value);
}

/// <inheritdoc/>
public IEnumerator<KeyValuePair<LanguageCode, string>> GetEnumerator()
=> (_texts ?? Enumerable.Empty<KeyValuePair<LanguageCode, string>>()).GetEnumerator();
=> NameContainer.Enumerate(container);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
8 changes: 5 additions & 3 deletions RogueLibsCore/Names/Localization/LanguageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,20 @@ public static LanguageCode FallBack
public static string? GetLanguageName(LanguageCode code)
=> languageNames.TryGetValue(code, out string? name) ? name : null;
/// <summary>
/// <para>Adds the specified <paramref name="languageName"/> to the game, and sets its language <paramref name="code"/>.</para>
/// <para>Adds the specified <paramref name="languageName"/> to the game, and returns its registered <see cref="LanguageCode"/> value.</para>
/// </summary>
/// <param name="languageName">The language name to add into the game.</param>
/// <param name="code">The language code that will be used to represent the language.</param>
public static void RegisterLanguageCode(string languageName, LanguageCode code)
/// <returns>The language code that will be used to represent the language.</returns>
public static LanguageCode RegisterLanguageCode(string languageName)
{
if (languageName is null) throw new ArgumentNullException(nameof(languageName));
if (languages.ContainsKey(languageName))
throw new ArgumentException($"The specified {nameof(languageName)} is already taken.", nameof(languageName));

LanguageCode code = (LanguageCode)languages.Count;
languages.Add(languageName, code);
languageNames.Add(code, languageName);
return code;
}

/// <summary>
Expand Down
129 changes: 129 additions & 0 deletions RogueLibsCore/Names/NameContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;

namespace RogueLibsCore
{
internal static class NameContainer
{
public static string? Get(object? container, LanguageCode code)
{
if (container is null) return null;
if (container is string text) return code == LanguageCode.English ? text : null;

if (container is string?[] array) return GetArray(array, code);
return GetDict((Dictionary<LanguageCode, string>)container, code);

static string? GetArray(string?[] array, LanguageCode code)
=> (int)code < array.Length ? array[(int)code] : null;

static string? GetDict(Dictionary<LanguageCode, string> dict, LanguageCode code)
=> dict.TryGetValue(code, out string? value) ? value : null;
}

[MustUseReturnValue]
public static object? Set(object? container, LanguageCode code, string? value)
{
if (container is null)
return value is null ? null : SetNew(code, value);
if (container is string text)
return SetString(text, code, value);
if (container is string?[] array)
return SetArray(array, code, value);
return SetDict((Dictionary<LanguageCode, string>)container, code, value);
}
private static object SetNew(LanguageCode code, string value)
{
if (code == LanguageCode.English) return value;
if ((int)code < 32)
{
string?[] array = new string?[LengthCeil8((int)code)];
array[(int)code] = value;
return array;
}
return new Dictionary<LanguageCode, string> { [code] = value };
}
private static object? SetString(string container, LanguageCode code, string? value)
{
if (code == LanguageCode.English) return value;
if (value is null) return container;
if ((int)code < 32)
{
string?[] array = new string?[LengthCeil8((int)code)];
array[(int)LanguageCode.English] = container;
array[(int)code] = value;
return array;
}
return new Dictionary<LanguageCode, string>
{
[LanguageCode.English] = container,
[code] = value,
};
}
private static object SetArray(string?[] array, LanguageCode code, string? value)
{
if ((int)code >= array.Length)
{
if (value is null) return array;
if ((int)code >= 32)
{
Dictionary<LanguageCode, string> dict = new();
for (int i = 0; i < array.Length; i++)
if (array[i] is not null)
dict[(LanguageCode)i] = array[i]!;
dict[code] = value;
return dict;
}
Array.Resize(ref array, LengthCeil8((int)code));
}
array[(int)code] = value;
return array;
}
private static object SetDict(Dictionary<LanguageCode, string> dict, LanguageCode code, string? value)
{
if (value is not null)
dict[code] = value;
else dict.Remove(code);
return dict;
}

public static object? Clone(object? container)
{
if (container is null) return null;
if (container is string) return container;
if (container is string?[] array) return array.ToArray();
return ((Dictionary<LanguageCode, string>)container).ToDictionary(static p => p);
}

public static IEnumerator<KeyValuePair<LanguageCode, string>> Enumerate(object? container)
{
if (container is null) yield break;
if (container is string text)
{
yield return new KeyValuePair<LanguageCode, string>(LanguageCode.English, text);
}
else if (container is string?[] array)
{
for (int i = 0; i < array.Length; i++)
{
if (array[i] is not null)
yield return new KeyValuePair<LanguageCode, string>((LanguageCode)i, array[i]!);
}
}
else
{
foreach (KeyValuePair<LanguageCode, string> entry in (Dictionary<LanguageCode, string>)container)
yield return entry;
}
}

private static int LengthCeil8(int num)
=> (num + 8) & ~0b111;

}
internal interface IUsesNameContainer
{
object? GetNameContainer();
}
}

0 comments on commit ac2151c

Please sign in to comment.