Browse Source

String substitution uses StringBuilder to avoid string allocations

master
Christopher 9 years ago
parent
commit
0eac532b35
  1. 33
      Assets/Fungus/Flowchart/Scripts/Flowchart.cs
  2. 14
      Assets/Fungus/Narrative/Scripts/Localization.cs
  3. 25
      Assets/Fungus/Thirdparty/FungusLua/Scripts/LuaUtils.cs
  4. 66
      Assets/Fungus/Thirdparty/FungusLua/Scripts/StringSubstituter.cs
  5. 3
      Assets/Tests/StringSubstitution/StringSubstitutionTests.unity
  6. 5
      ProjectSettings/ProjectSettings.asset

33
Assets/Fungus/Flowchart/Scripts/Flowchart.cs

@ -2,6 +2,7 @@ using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using System;
using System.Text;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
@ -968,15 +969,15 @@ namespace Fungus
* use the SubstituteVariables() method instead.
*/
[MoonSharp.Interpreter.MoonSharpHidden]
public virtual string SubstituteStrings(string input)
public virtual bool SubstituteStrings(StringBuilder input)
{
string subbedText = input;
// Instantiate the regular expression object.
Regex r = new Regex("{\\$.*?}");
bool modified = false;
// Match the regular expression pattern against a text string.
var results = r.Matches(input);
var results = r.Matches(input.ToString());
foreach (Match match in results)
{
string key = match.Value.Substring(2, match.Value.Length - 3);
@ -991,12 +992,14 @@ namespace Fungus
variable.key == key)
{
string value = variable.ToString();
subbedText = subbedText.Replace(match.Value, value);
input.Replace(match.Value, value);
modified = true;
}
}
}
return subbedText;
return modified;
}
/**
@ -1012,8 +1015,11 @@ namespace Fungus
stringSubstituer = new StringSubstituter();
}
string subbedText = input;
// Use the string builder from StringSubstituter for efficiency.
StringBuilder sb = stringSubstituer.stringBuilder;
sb.Length = 0;
sb.Append(input);
// Instantiate the regular expression object.
Regex r = new Regex("{\\$.*?}");
@ -1033,13 +1039,20 @@ namespace Fungus
variable.key == key)
{
string value = variable.ToString();
subbedText = subbedText.Replace(match.Value, value);
sb.Replace(match.Value, value);
}
}
}
// Now do all other substitutions in the scene
return stringSubstituer.SubstituteStrings(subbedText);
if (stringSubstituer.SubstituteStrings(sb))
{
return sb.ToString();
}
else
{
return input;
}
}
}

14
Assets/Fungus/Narrative/Scripts/Localization.cs

@ -6,6 +6,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using System.IO;
using Ideafixxxer.CsvParser;
@ -525,19 +526,19 @@ namespace Fungus
* Implementation of StringSubstituter.ISubstitutionHandler.
* Relaces tokens of the form {$KeyName} with the localized value corresponding to that key.
*/
public virtual string SubstituteStrings(string input)
public virtual bool SubstituteStrings(StringBuilder input)
{
// This method could be called from the Start method of another component, so we
// may need to initilize the localization system.
Init();
string subbedText = input;
// Instantiate the regular expression object.
Regex r = new Regex("{\\$.*?}");
bool modified = false;
// Match the regular expression pattern against a text string.
var results = r.Matches(input);
var results = r.Matches(input.ToString());
foreach (Match match in results)
{
string key = match.Value.Substring(2, match.Value.Length - 3);
@ -546,11 +547,12 @@ namespace Fungus
string localizedString = Localization.GetLocalizedString(key);
if (localizedString != null)
{
subbedText = subbedText.Replace(match.Value, localizedString);
input.Replace(match.Value, localizedString);
modified = true;
}
}
return subbedText;
return modified;
}
}

25
Assets/Fungus/Thirdparty/FungusLua/Scripts/LuaUtils.cs vendored

@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
using MoonSharp.Interpreter;
@ -319,7 +320,7 @@ namespace Fungus
/// The string table value used depends on the currently loaded string table and active language.
/// </summary>
[MoonSharpHidden]
public virtual string SubstituteStrings(string input)
public virtual bool SubstituteStrings(StringBuilder input)
{
// This method could be called from the Start of another component, so
// we need to ensure that the LuaEnvironment has been initialized.
@ -333,26 +334,26 @@ namespace Fungus
}
if (luaEnvironment == null)
{
{
UnityEngine.Debug.LogError("No Lua Environment found");
return input;
return false;
}
if (luaEnvironment.Interpreter == null)
{
UnityEngine.Debug.LogError("No Lua interpreter found");
return input;
return false;
}
MoonSharp.Interpreter.Script interpreter = luaEnvironment.Interpreter;
string subbedText = input;
// Instantiate the regular expression object.
Regex r = new Regex("\\{\\$.*?\\}");
bool modified = false;
// Match the regular expression pattern against a text string.
var results = r.Matches(input);
var results = r.Matches(input.ToString());
foreach (Match match in results)
{
string key = match.Value.Substring(2, match.Value.Length - 3);
@ -366,7 +367,8 @@ namespace Fungus
DynValue languageEntry = stringTableVar.Table.Get(activeLanguage);
if (languageEntry.Type == DataType.String)
{
subbedText = subbedText.Replace(match.Value, languageEntry.String);
input.Replace(match.Value, languageEntry.String);
modified = true;
}
continue;
}
@ -376,12 +378,13 @@ namespace Fungus
DynValue globalVar = interpreter.Globals.Get(key);
if (globalVar.Type != DataType.Nil)
{
subbedText = subbedText.Replace(match.Value, globalVar.ToPrintString());
input.Replace(match.Value, globalVar.ToPrintString());
modified = true;
continue;
}
}
return subbedText;
return modified;
}
/// <summary>
@ -390,7 +393,7 @@ namespace Fungus
/// </summary>
public virtual string Substitute(string input)
{
return stringSubstituter.SubstituteStrings(input);
return stringSubstituter.SubstituteStrings(input);
}
/// <summary>

66
Assets/Fungus/Thirdparty/FungusLua/Scripts/StringSubstituter.cs vendored

@ -17,21 +17,33 @@ namespace Fungus
public interface ISubstitutionHandler
{
/// <summary>
/// Returns a new string with tokens replaced by subsituted values.
/// Modifies a StringBuilder so that tokens are replaced by subsituted values.
/// It's up to clients how to implement substitution but the convention looks like:
/// "Hi {$VarName}" => "Hi John" where VarName == "John"
/// <returns>True if the input was modified</returns>
/// </summary>
string SubstituteStrings(string input);
bool SubstituteStrings(StringBuilder input);
}
protected List<ISubstitutionHandler> substitutionHandlers = new List<ISubstitutionHandler>();
/**
* The StringBuilder instance used to substitute strings optimally.
* This property is public to support client code optimisations.
*/
public StringBuilder stringBuilder;
private int recursionDepth;
/// <summary>
/// Constructor which caches all components in the scene that implement ISubstitutionHandler.
/// <param name="recursionDepth">Number of levels of recursively embedded keys to resolve.</param>
/// </summary>
public StringSubstituter()
public StringSubstituter(int recursionDepth = 5)
{
CacheSubstitutionHandlers();
stringBuilder = new StringBuilder(1024);
this.recursionDepth = recursionDepth;
}
/// <summary>
@ -64,28 +76,48 @@ namespace Fungus
/// </summary>
public virtual string SubstituteStrings(string input)
{
string newString = input;
stringBuilder.Length = 0;
stringBuilder.Append(input);
if (SubstituteStrings(stringBuilder))
{
return stringBuilder.ToString();
}
else
{
return input; // String wasn't modified
}
}
public virtual bool SubstituteStrings(StringBuilder input)
{
bool result = false;
// Perform the substitution multiple times to expand nested keys
int lasthash = 0;
int currenthash = -1;
int loopCount = 0; // Avoid infinite recursion loops
while (lasthash != currenthash &&
loopCount < 5)
while (loopCount < recursionDepth)
{
lasthash = newString.GetHashCode();
foreach (ISubstitutionHandler handler in substitutionHandlers)
{
newString = handler.SubstituteStrings(newString);
}
currenthash = newString.GetHashCode();
bool modified = false;
foreach (ISubstitutionHandler handler in substitutionHandlers)
{
if (handler.SubstituteStrings(input))
{
modified = true;
result = true;
}
}
if (!modified)
{
break;
}
loopCount++;
}
return newString;
}
return result;
}
}
}

3
Assets/Tests/StringSubstitution/StringSubstitutionTests.unity

@ -1427,6 +1427,7 @@ MonoBehaviour:
- Fungus.Command, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
- Fungus.CommandInfoAttribute, Assembly-CSharp, Version=0.0.0.0, Culture=neutral,
PublicKeyToken=null
- System.Text.StringBuilder, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
- UnityEngine.Rect, UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
boundObjects:
- key: text
@ -1592,7 +1593,7 @@ GameObject:
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
m_IsActive: 0
--- !u!114 &1475498171
MonoBehaviour:
m_ObjectHideFlags: 0

5
ProjectSettings/ProjectSettings.asset

@ -117,6 +117,8 @@ PlayerSettings:
m_Bits: 238
iPhoneSdkVersion: 988
iPhoneTargetOSVersion: 22
tvOSSdkVersion: 0
tvOSTargetOSVersion: 900
uIPrerenderedIcon: 0
uIRequiresPersistentWiFi: 0
uIRequiresFullScreen: 1
@ -199,6 +201,7 @@ PlayerSettings:
wiiUSystemHeapSize: 128
wiiUTVStartupScreen: {fileID: 0}
wiiUGamePadStartupScreen: {fileID: 0}
wiiUDrcBufferDisabled: 0
wiiUProfilerLibPath:
actionOnDotNetUnhandledException: 1
enableInternalProfiler: 0
@ -266,6 +269,7 @@ PlayerSettings:
ps4DownloadDataSize: 0
ps4GarlicHeapSize: 2048
ps4Passcode: 5PN2qmWqBlQ9wQj99nsQzldVI5ZuGXbE
ps4UseDebugIl2cppLibs: 0
ps4pnSessions: 1
ps4pnPresence: 1
ps4pnFriends: 1
@ -331,6 +335,7 @@ PlayerSettings:
psp2UseLibLocation: 0
psp2InfoBarOnStartup: 0
psp2InfoBarColor: 0
psp2UseDebugIl2cppLibs: 0
psmSplashimage: {fileID: 0}
spritePackerPolicy:
scriptingDefineSymbols: {}

Loading…
Cancel
Save