2017年8月4日 星期五

多國語系-大量本文的處理問題



最近在工作上的專案因為使用了多國語系的功能,所以需要處理大量文字,但同時也造成了遊戲初始開啟時,卡偵的情況很嚴重。所以需要好好思考一下,要怎麼優化這個小地方。




規格是採用.csv ,用逗號分割字串,並含有三個語系。

(這邊是用Fungus的Localization系統)


KeyDescriptionChineseChinaEnglish

.初次碰到的問題
原有的Fungus套件裡的Localization 系統,存在一個嚴重的問題,每當切換語系的時候,他會重新載入文字,並只取對應的語言儲到 Dictionary< Key, Value > 裡,量小的時候還好,但是當文字量達到上萬筆時,每次的切換都是一陣等待。


文字解析的步驟
.取得Csv全部的文字
.文字做Split("\r\n") 後,存成LineArray
.各LineArray再 Split(','),以逗號分割存成 InfoArray 取出所需資料
.InfoArray[0] 必為 Key值
.截取索引的欄位的值存成Value
.回存到字典 Dictionary.Add(Key , Value);

// 原先的結構
protected static Dictionary<string string=""> localizedStrings = new Dictionary<string string="">();

public static GetLocalization(string Key)
{
    if (localizedStrings.Contanins(Key))
        return localizedString[Key];
} 



原本的執行速度


因此我在去年改寫過一次,將資料結構改成 Dictionary < Key, Value[]> ,並記錄最後使用的語系索引值,雖然佔的內存變大了,而且整體速度沒變,但是之後切語系都是順暢的。

文字解析的步驟
.取得文字
.文字做Split("\r\n") 後,存成LineArray
.各LineArray再 Split(','),以逗號分割存成 InfoArray 取出所需資料
.InfoArray[0] 必為 Key值
.將取出的 InfoArray當做Value[] 存在字典裡
.回存到字典 Dictionary.Add(Key , Value[]);

// 變更後的結構

protected static Dictionary<string, string[]> localizedStrings = new Dictionary<string, string[]>();

protected int LanguageIndex = 0;

//取得內文
public static GetLocalization(string Key)
{
    if (localizedStrings.Contanins(Key))
    {
        if (localizedString.Length > LanguageIndex)
            return localizedString[Key][LanguageIndex];
    }
    return Key;
}


.後來隨著專案變的更大,資料量更多時,初次載入的時候已造成影響…

雖然解決了後續轉換語言的問題,但因為內容變的更多了,初次載入已造成嚴重的影響,所以今天試著改寫一下一開始解析的架構,並加上一些小優化。

*這邊造成的影響是因為做Split時,程式語言其實是用IndexOf、SubString 等去查找我們給的字元,因此現在我們的內文有高達500多萬字元時…其執行效率是非常差的。

/// 多國語系記錄類別
public class LocalizationInfo
{
    public string BaseString;
    private string[] LocalizationInfos;

    public LocalizationInfo (string BaseString)
    {
        this.BaseString = BaseString;
    }
    public string GetLocalization(int CodeIndex , string DefaultString)
    {
        if (LocalizationInfos == null || LocalizationInfos.Length < 1)
            LocalizationInfos = BaseString.Split(',');
        if (LocalizationInfos.Length > CodeIndex)
            return LocalizationInfos[CodeIndex];

        return DefaultString;
    }
}

這邊加了一個類別,我想把Split的次數減少,以目前現有專案2萬筆,每筆5個逗號,光一行就要SubString *5次 + IndexOf *5 次, 2萬筆下來可就數十萬次,而且估計每次SubString 都是從原本的本文Index 0 開始算起… 這開銷真的蠻可怕的。
(有錯請糾正,謝謝)

因此目前打算把執行次數壓到總行數,先把資料準備好,所以這邊的建構子傳入的是該"單行全部的內容"
待要用到的時候才進行Split 分割至預存的 Array[] 裡…畢竟多國語系2萬多筆,玩家玩一次可能才看的到幾十筆、幾百筆也說不一定。

*這裡甚至可以考慮加入一段時間後,把Array的記憶體釋放出來。


// 修改的結構
protected static Dictionary<string, LocalizationInfo> localizedStrings = new Dictionary<string, LocalizationInfo>();

//解析部份

public static void InitLocalization(TextAsset localizationFile)
{
    string[] fLines = Regex.Split(localizationFile.text, "\r\n");
    int TotalLines = fLines.Length;
    int LineCount = 0;
            
    string CurrentLineString = null;
    string TempKey = "";
    do
    {
        CurrentLineString = null;
        CurrentLineString = fLines[LineCount];

        if (LineCount == 0)
        {
            // Key欄的資料
            LanguageCode = CurrentLineString.Split(',');
        }
        try
        {
            TempKey = CurrentLineString.Substring(0, CurrentLineString.IndexOf(','));
            if (localizedStrings.ContainsKey(TempKey) == false)
            {
                localizedStrings.Add(TempKey, new LocalizationInfo(CurrentLineString));
            }
        }
        catch
        {
            Debug.Error(string.Format("%u7B2C{0}%u884C%u6709%u554F%u984C-> {1}",LineCount , CurrentLineString));

        }

        LineCount  ;
    }
    while (LineCount < TotalLines);
}
這邊解析的部份直接改寫,用正規表達式截取出一行一行。

由於多國語系第一行必為Key欄,所以直接在LineCount = 0 時做處理,其餘資料被建構成LocalizationInfo。

所以這邊的流程變為
.取得文字
.文字做正規表達式後,取出單行
.將單行存於 LocalizationInfo 的基本文字
.Key值為 該行文字.Substring(0, 該行文字.IndexOf(','));
.回存至字典 Dictionary.Add(Key , new LocalizationInfo(該行文字));
.要使用時,在去做細部Split


//取得內文
public static GetLocalization(string Key)
{
    if (localizedStrings.Contanins(Key))
    {
        if (localizedString.Length > LanguageIndex)
            return localizedStrings[Key].GetLocalization(LanguageIndex,Key);
    }
    return Key;
}


調整後的執行速度



以上是此次修正的方向,雖然內存可能變多了,但初始的速度整體變快許多。

======================================================================

後記:
其實對於處理大量文字一定還有更好的作法…看看搜尋引擎…不過我就沒有太深入,直到下次這個方法又碰到瓶頸,我才會去想辦法解決吧?

如果有人想直接留言給我關鍵字的話也非常歡迎~(不如說 拜託,請教我 QQ )

這個工具雖然是用Fungus套件改的,不過基本上把上面的架構拆出去…正規表達式規則自定義,Split 規則自字義,應該是有潛力變成一個共用形式的Class 類吧?

不過我的精神止於解決瓶頸…XD,接下來還是繼續寫遊戲的新機制吧,對我來說比較有趣 😄

Unity Fungus 套件
官網:http://fungusgames.com/
Asset Store:https://www.assetstore.unity3d.com/en/#!/content/34184

沒有留言:

張貼留言