├── CacheStrings.cs ├── LICENSE ├── LazyCacheStrings.cs └── README.md /CacheStrings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Debug = UnityEngine.Debug; 4 | using Assert = UnityEngine.Assertions.Assert; 5 | 6 | public class CacheStrings 7 | { 8 | protected Dictionary _table; 9 | protected Func _hashToString; 10 | protected Func _hashFunction; 11 | 12 | public CacheStrings ( Func hashFunction , Func hashToString ) : this( hashFunction , hashToString , 100 ) {} 13 | public CacheStrings ( Func hashFunction , Func hashToString , int initialDictCapacity ) 14 | { 15 | Assert.IsNotNull( hashFunction ); 16 | Assert.IsNotNull( hashToString ); 17 | _table = new Dictionary( initialDictCapacity ); 18 | _hashFunction = hashFunction; 19 | _hashToString = hashToString; 20 | } 21 | 22 | public string this [ KEY key ] 23 | { 24 | get 25 | { 26 | string result; 27 | HASH hash = _hashFunction( key ); 28 | if( _table.TryGetValue( hash , out result )==false )//fail safe 29 | { 30 | result = _hashToString( hash ); 31 | Assert.IsNotNull( result ); 32 | _table.Add( hash , result ); 33 | } 34 | return result; 35 | } 36 | } 37 | 38 | } 39 | 40 | public sealed class CacheIntString : CacheStrings 41 | { 42 | public CacheIntString 43 | ( 44 | Func hashFunction , 45 | Func hashToString , 46 | int initMin , 47 | int initMax , 48 | int initStep 49 | ) 50 | : base( hashFunction , hashToString , initStep!=0 ? (initMax-initMin)/initStep : 0 ) 51 | { 52 | if( initStep!=0 ) 53 | for( int key=initMin ; key<=initMax ; key+=initStep ) 54 | { 55 | int hash = hashFunction( key ); 56 | string str = hashToString( hash ); 57 | Assert.IsNotNull( str ); 58 | if( _table.ContainsKey( hash )==false ) _table.Add( hash , str ); 59 | else Debug.LogWarning( $"Redundant key: { key }, where hash: { hash }" ); 60 | } 61 | } 62 | } 63 | 64 | public sealed class CacheUIntString : CacheStrings 65 | { 66 | public CacheUIntString 67 | ( 68 | Func hashFunction , 69 | Func hashToString , 70 | uint initMin , 71 | uint initMax , 72 | uint initStep 73 | ) 74 | : base( hashFunction , hashToString , Convert.ToInt32(initStep!=0 ? (initMax-initMin)/initStep : 0) ) 75 | { 76 | if( initStep!=0 ) 77 | for( uint key=initMin ; key<=initMax ; key+=initStep ) 78 | { 79 | uint hash = hashFunction( key ); 80 | string str = hashToString( hash ); 81 | Assert.IsNotNull( str ); 82 | if( _table.ContainsKey( hash )==false ) _table.Add( hash , str ); 83 | else Debug.LogWarning( $"Redundant key: { key }, where hash: { hash }" ); 84 | } 85 | } 86 | } 87 | 88 | public sealed class CacheULongString : CacheStrings 89 | { 90 | public CacheULongString 91 | ( 92 | Func hashFunction , 93 | Func hashToString , 94 | ulong initMin , 95 | ulong initMax , 96 | ulong initStep 97 | ) 98 | : base( hashFunction , hashToString , Convert.ToInt32(initStep!=0 ? (initMax-initMin)/initStep : 0) ) 99 | { 100 | if( initStep!=0 ) 101 | for( ulong key=initMin ; key<=initMax ; key+=initStep ) 102 | { 103 | ulong hash = hashFunction( key ); 104 | string str = hashToString( hash ); 105 | Assert.IsNotNull( str ); 106 | if( _table.ContainsKey( hash )==false ) _table.Add( hash , str ); 107 | else Debug.LogWarning( $"Redundant key: { key }, where hash: { hash }" ); 108 | } 109 | } 110 | } 111 | 112 | public sealed class CacheDoubleString : CacheStrings 113 | { 114 | public CacheDoubleString 115 | ( 116 | Func hashFunction , 117 | Func hashToString , 118 | double initMin , 119 | double initMax , 120 | double initStep 121 | ) 122 | : base( hashFunction , hashToString , Convert.ToInt32(initStep!=0 ? (initMax-initMin)/initStep : 0) ) 123 | { 124 | if( initStep!=0 ) 125 | for( double key=initMin ; key<=initMax ; key+=initStep ) 126 | { 127 | double hash = hashFunction( key ); 128 | string str = hashToString( hash ); 129 | Assert.IsNotNull( str ); 130 | if( _table.ContainsKey( hash )==false ) _table.Add( hash , str ); 131 | else Debug.LogWarning( $"Redundant key: { key }, where hash: { hash }" ); 132 | } 133 | } 134 | } 135 | 136 | public sealed class CacheDoubleIntString : CacheStrings 137 | { 138 | public CacheDoubleIntString 139 | ( 140 | Func hashFunction , 141 | Func hashToString , 142 | double initMin , 143 | double initMax , 144 | double initStep 145 | ) 146 | : base( hashFunction , hashToString , Convert.ToInt32(initStep!=0 ? (initMax-initMin)/initStep : 0) ) 147 | { 148 | if( initStep!=0 ) 149 | for( double key=initMin ; key<=initMax ; key+=initStep ) 150 | { 151 | int hash = hashFunction( key ); 152 | string str = hashToString( hash ); 153 | Assert.IsNotNull( str ); 154 | if( _table.ContainsKey( hash )==false ) _table.Add( hash , str ); 155 | else Debug.LogWarning( $"Redundant key: { key }, where hash: { hash }" ); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Andrew Raphael Łukasik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LazyCacheStrings.cs: -------------------------------------------------------------------------------- 1 | // creation: 2 | // public static LazyCacheStrings MyIntStrings = new LazyCacheStrings( (t)=>t.ToString() ); 3 | // usage: 4 | // string myString = MyIntStrings[ someIntVariable ]; 5 | 6 | /// Most basic/approachable/triage way of string caching. 7 | public class LazyCacheStrings 8 | { 9 | 10 | System.Collections.Generic.Dictionary _lookup; 11 | readonly System.Func _toString; 12 | 13 | public LazyCacheStrings ( System.Func toString , int initialCapacity = 1000 ) 14 | { 15 | this._toString = toString; 16 | this._lookup = new System.Collections.Generic.Dictionary( initialCapacity ); 17 | } 18 | 19 | public string this [ T key ] 20 | { 21 | get 22 | { 23 | if( _lookup.TryGetValue( key , out string result )==false ) 24 | { 25 | result = _toString( key ); 26 | _lookup.Add( key , result ); 27 | } 28 | return result; 29 | } 30 | } 31 | 32 | public void Clear () { _lookup.Clear(); } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CacheStrings 2 | Will help you fix all those UI-related wasteful allocations and GC bumps - hardly noticeable on PC but costly for mobile VR/AR/XR. 3 | 4 | How? Pre-generate all the string variants **once** and replace runtime allocations with a single dictionary lookup. Constant cost, GC bumps are gone; problem solved. 5 | 6 | Use cases: 7 | - Timers (!) 8 | - Any kind of UI fields where number of variants is limited & those can be pre-generated procedurally 9 | 10 | # How does it work? 11 | You provide a hash function and a string formatting method - all in a constructor call during field initialization. This will generate all the variants. 12 | Once you want to get a string use index operator `text = cache[key]` 13 | 14 | Whole idea is basically this, but encapsulated in a class: 15 | ```C# 16 | Dictionary table; 17 | Func hashToString; 18 | Func hashFunction; 19 | ``` 20 | # How to use it? 21 | ```C# 22 | using System; 23 | using UnityEngine; 24 | using UnityEngine.UI; 25 | 26 | public class TestCacheIntString : MonoBehaviour 27 | { 28 | 29 | public Text _displaySeconds; 30 | public Text _displayMinutes; 31 | public Text _displayHours; 32 | public Text _displayDays; 33 | public Text _displayMilliSeconds; 34 | public double _time = 0; 35 | 36 | CacheIntString cacheSeconds = new CacheIntString( 37 | (seconds)=>seconds%60 , //describe how seconds (key) will be translated to useful value (hash) 38 | (second)=>second.ToString("00") //you describe how string is built based on given value (hash) 39 | , 0 , 59 , 1 //initialization range and step, so cache will be warmed up and ready 40 | ); 41 | 42 | CacheIntString cacheMinutes = new CacheIntString( 43 | (seconds)=>seconds/60%60 , // this translates input seconds to minutes 44 | (minute)=>minute.ToString("00") // this translates minute to string 45 | , 0 , 60 , 60 //minutes needs a step of 60 seconds 46 | ); 47 | 48 | CacheIntString cacheHours = new CacheIntString( 49 | (seconds)=>seconds/(60*60)%24 , // this translates input seconds to hours 50 | (hour)=>hour.ToString("00") , // this translates hour to string 51 | 0 , 24 , 60*60 //hours needs a step of 60*60 seconds 52 | ); 53 | 54 | CacheIntString cacheDays = new CacheIntString( 55 | (seconds)=>seconds/(60*60*24) , // this translates input seconds to days 56 | (day)=>day.ToString() , // this translates day to string 57 | 0 , 31 , 60*60*24 //days needs a step of 60*60*24 seconds 58 | ); 59 | 60 | CacheDoubleIntString cacheMilliSeconds = new CacheDoubleIntString( 61 | (seconds)=>(int)System.Math.Round(seconds%1d*1000d) , //extract 3 decimal places 62 | (second)=>second.ToString("000") 63 | , 0d , 0.999d , 0.001d //1ms step 64 | ); 65 | 66 | void Update () 67 | { 68 | _time += Time.deltaTime; 69 | int seconds = Mathf.FloorToInt( (float)_time ); 70 | 71 | _displaySeconds.text = cacheSeconds[ seconds ]; 72 | _displayMinutes.text = cacheMinutes[ seconds ]; 73 | _displayHours.text = cacheHours[ seconds ]; 74 | _displayDays.text = cacheDays[ seconds ]; 75 | _displayMilliSeconds.text = cacheMilliSeconds[ _time ]; 76 | } 77 | 78 | } 79 | ``` 80 | # 81 | --------------------------------------------------------------------------------