using System.Collections.Generic; using UnityEngine; // Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved. namespace scsmmedia { /// /// This class provides a subset of functions of string class which /// will do less memory allocations and should result in less garabage. /// It is created for specific use within SCSM assets. /// NOTE: It only deals with fixed length strings. /// public class SCSMString { #region private variables private char[] charArray = null; private int currentCapacity = 0; private string outputString = null; // Is the output ok or does it need to be updated? private bool isDirty = true; private int endIdxPos = 0; private char[] numArray = null; private int intMaxLength = 9; private int intMaxValue = 0; #endregion #region Constructors /// /// Create a string and pre-allocate enough space or capacity /// for the expected whole string. /// /// public SCSMString(int capacity) { // allocate space for the total string currentCapacity = capacity; charArray = new char[currentCapacity]; } #endregion #region Private Methods /// /// Faster version of Mathf.Pow for integer exponents and bases /// Works with pow range 0 to 9. /// /// /// /// private int IntPow(int num, int pow) { if (pow != 0) { int ans = num; for (int i = 1; i < pow; i++) { ans *= num; } return ans; } else { return 1; } } #endregion #region Public Methods /// /// Set the contents of the current string. This will overwrite /// any current contents in the string. /// Characters that overflow the capacity will be truncated /// /// public void Set(string contents) { Empty(); Add(contents); } /// /// Add an integer to the end of the current string. Numbers that /// don't fit will be truncated. If leftJustify is false and the number /// is positive, a space will be inserted before the number. /// /// public void Set(int value, bool leftJustify = true) { Empty(); Add(value, leftJustify); } /// /// Add a string onto the end of the current string. Characters /// that don't fit will be truncated. /// NOTE: this is not the same as Append (coming later). /// /// public void Add(string contents) { int contentsLength = contents.Length; // will the for-loop update the array? if (contentsLength > 0 && endIdxPos < currentCapacity) { isDirty = true; } for (int i = 0; i < contentsLength && endIdxPos < currentCapacity; i++) { charArray[endIdxPos] = contents[i]; endIdxPos++; } } /// /// Add a char to the end of the current string. If it doesn't fit, /// it will not be added. /// /// public void Add(char value) { if (endIdxPos < currentCapacity) { charArray[endIdxPos++] = value; isDirty = true; } } /// /// Add a space to the end of the current string. If it doesn't fit, /// it will not be added. /// public void AddSpace() { if (endIdxPos < currentCapacity) { charArray[endIdxPos++] = ' '; isDirty = true; } } /// /// Add an integer to end of the current string. Numbers that /// don't fit will be truncated. If leftJustify is false and the number /// is positive, a space will be inserted before the number. /// Limits: +/- 999,999,999 /// /// /// public void Add(int value, bool leftJustify = true) { if (value < 0) { Add("-"); value = -value; } // if +ve and right justified, add a space for the (missing) sign else if (!leftJustify) { Add(" "); } if (numArray == null) { SetMaxIntLength(intMaxLength); } if (value < intMaxValue + 1 && numArray != null) { int numDigits = 0; // fill array with null characters for safety for (int i=0; i < intMaxLength; i++) { numArray[i] = '\0'; } // We don't know how many digits there are just yet, so process the number in reverse do { // ASCII (and therefore ANSI) codes for numerals go from 48 (0) to 57 (9) numArray[numDigits++] = (char)(48 + value % 10); // Remove the last digit value /= 10; } while (value != 0); // Copy them into the actual array for (int i = numDigits-1; i >= 0 && endIdxPos < currentCapacity; i--) { charArray[endIdxPos++] = numArray[i]; } } isDirty = true; } /// /// Empty or clear the string /// public void Empty() { endIdxPos = 0; isDirty = true; } /// /// Is the string empty? /// /// public bool IsEmpty() { return endIdxPos == 0; } /// /// When adding integers to the string, an array is created to hold the /// incoming numerals before they are add to the string. By default this /// is created with 10 digits if/when the first Add(int) is called. /// To change this allocation, call this before first calling Add(int). /// /// public void SetMaxIntLength(int value) { // Max int should be 2,147,483,647 but our faster IntPow() only returns max 1.4 billion for some reason // So set limit to 9 digits rather than 10. if (value < 0 || value > 9) { #if UNITY_EDITOR Debug.LogWarning("ERROR: SCSMString.SetMaxIntLength value must be between 0 and 9"); #endif } else if (value > 0 && numArray == null) { intMaxLength = value; numArray = new char[intMaxLength]; intMaxValue = IntPow(10, intMaxLength) - 1; //intMaxValue = (int)System.Math.Pow(10, intMaxLength) - 1; } } public override string ToString() { // The output string needs to be update if (isDirty) { isDirty = false; outputString = new string(charArray, 0, endIdxPos); } return outputString; } #endregion #region Static Format Time methods /// /// Returns the time given the hours and minutes /// Displays -- if the values exceed their limits. /// For the sake of performance, assumes all values are +ve. /// /// /// /// public static string FormatTime(int hours, int minutes) { SCSMString sg = new SCSMString(6); if (hours == 0) { sg.Set("00"); } else if (hours < 10) { sg.Set("0"); sg.Add(hours); } else if (hours < 99) { sg.Set(hours); } else { sg.Set("--"); } sg.Add(":"); if (minutes > 59) { sg.Add("--"); } else if (minutes > 9) { sg.Add(minutes); } else { sg.Add("0"); sg.Add(minutes); } return sg.ToString(); } /// /// Returns the time given the hours, minutes, and seconds /// Displays -- if the values exceed their limits. /// For the sake of performance, assumes all values are +ve. /// /// /// /// /// public static string FormatTime(int hours, int minutes, int secs) { SCSMString sg = new SCSMString(15); // Deal with most common scenarios first if (hours == 0) { sg.Set("00"); } else if (hours < 10) { sg.Set("0"); sg.Add(hours); } else if (hours < 99) { sg.Set(hours); } else { sg.Set("--"); } sg.Add(":"); if (minutes > 59) { sg.Add("--"); } else if (minutes > 9) { sg.Add(minutes); } else { sg.Add("0"); sg.Add(minutes); } sg.Add(":"); if (secs > 59) { sg.Add("--"); } else if (secs > 9) { sg.Add(secs); } else { sg.Add("0"); sg.Add(secs); } return sg.ToString(); } /// /// Return the time given the hours, minutes, seconds and milliseconds. /// Show msecs as tenths or hundredths of a second. /// Display -- or --- if the values exceed their limits. /// For the sake of performance, assumes all values are +ve. /// /// /// /// /// /// /// public static string FormatTime(int hours, int minutes, int secs, int msecs, bool showTenths) { // Equivalent to: String.Format("{0:00}:{1:00}:{2:00}." + (showTenths ? "{3:00}" : "{3:000}"), hours, minutes, seconds, msecs); SCSMString sg = new SCSMString(15); // Deal with most common scenarios first if (hours == 0) { sg.Set("00"); } else if (hours < 10) { sg.Set("0"); sg.Add(hours); } else if (hours < 99) { sg.Set(hours); } else { sg.Set("--"); } sg.Add(":"); if (minutes > 59) { sg.Add("--"); } else if (minutes > 9) { sg.Add(minutes); } else { sg.Add("0"); sg.Add(minutes); } sg.Add(":"); if (secs > 59) { sg.Add("--"); } else if (secs > 9) { sg.Add(secs); } else { sg.Add("0"); sg.Add(secs); } sg.Add("."); if (showTenths) { if (msecs > 99) { sg.Add("--"); } else if (msecs > 9) { sg.Add(msecs); } else { sg.Add("0"); sg.Add(msecs); } } else { if (msecs > 999) { sg.Add("---"); } else if (msecs > 99) { sg.Add(msecs); } else if (msecs > 9) { sg.Add("0"); sg.Add(msecs); } else { sg.Add("00"); sg.Add(msecs); } } return sg.ToString(); } /// /// Return the time given the minutes, seconds and milliseconds. /// Show msecs as tenths or hundredths of a second. /// Display -- or --- if the values exceed their limits. /// For the sake of performance, assumes all values are +ve. /// /// /// /// /// /// public static string FormatTime(int minutes, int secs, int msecs, bool showTenths) { // Equivalent to: String.Format("{0:00}:{1:00}." + (showTenths ? "{2:00}" : "{2:000}"), minutes, seconds, msecs); SCSMString sg = new SCSMString(10); if (minutes > 59) { sg.Set("--"); } else if (minutes > 9) { sg.Set(minutes); } else { sg.Set("0"); sg.Add(minutes); } sg.Add(":"); if (secs > 59) { sg.Add("--"); } else if (secs > 9) { sg.Add(secs); } else { sg.Add("0"); sg.Add(secs); } sg.Add("."); if (showTenths) { if (msecs > 99) { sg.Add("--"); } else if (msecs > 9) { sg.Add(msecs); } else { sg.Add("0"); sg.Add(msecs); } } else { if (msecs > 999) { sg.Add("---"); } else if (msecs > 99) { sg.Add(msecs); } else if (msecs > 9) { sg.Add("0"); sg.Add(msecs); } else { sg.Add("00"); sg.Add(msecs); } } return sg.ToString(); } #endregion } }