Initializing many Vector arrays in constructor causes stack overflow

Hello everyone,
I have this problem. I want to draw text characters and symbols with debug lines. Therefore I store Vector jagged arrays that represent the shape of the text characters and shapes in a class. In my class it looks like this:

        public Vector3[][] char_a = { new Vector3[] { new Vector2(0.723f, 0.165f), new Vector2(0.539f, 0.035f), new Vector2(0.263f, 0.037f), new Vector2(0.173f, 0.1421f), new Vector2(0.1761f, 0.2576f), new Vector2(0.3387f, 0.3954f), new Vector2(0.5582f, 0.4002f), new Vector2(0.7232f, 0.3654f) }, new Vector3[] { new Vector2(0.25f, 0.641f), new Vector2(0.397f, 0.698f), new Vector2(0.586f, 0.697f), new Vector2(0.724f, 0.579f), new Vector2(0.724f, 0.044f), new Vector2(0.84f, 0.044f) } };
        public Vector3[][] char_b = { new Vector3[] { new Vector2(0.225f, 0.256f), new Vector2(0.458f, 0.035f), new Vector2(0.644f, 0.037f), new Vector2(0.795f, 0.1421f), new Vector2(0.873f, 0.289f), new Vector2(0.875f, 0.438f), new Vector2(0.791f, 0.583f), new Vector2(0.651f, 0.692f), new Vector2(0.465f, 0.692f), new Vector2(0.226f, 0.481f) }, new Vector3[] { new Vector2(0.1f, 0.972f), new Vector2(0.23f, 0.966f), new Vector2(0.227f, 0.042f), new Vector2(0.122f, 0.043f) } };
        public Vector3[][] char_c = { new Vector3[] { new Vector2(0.877f, 0.158f), new Vector2(0.653f, 0.035f), new Vector2(0.425f, 0.037f), new Vector2(0.265f, 0.147f), new Vector2(0.1991f, 0.289f), new Vector2(0.2052f, 0.438f), new Vector2(0.279f, 0.595f), new Vector2(0.4362f, 0.692f), new Vector2(0.6296f, 0.692f), new Vector2(0.7598f, 0.6229f), new Vector2(0.8288f, 0.511f), new Vector2(0.826f, 0.6743f) } };

This is only a small fraction of all Vector3 jagged arrays, I have more than 100 symbols, and each of them has a Vector3[ ][ ] line that defines it.

Now there is sometimes the problem that I cannot enter playmode because the class that holds the vector arrays has a stack overflow in its constructor. I think the vector array initialization is automatically moved to the constructor and all the jagged arrays with the vectors fill up the stack ultimately leading to the stack overflow.

How can I get around this? Is there a way to have so much data accessible in a script without reaching the stack overflow limit?

(by the way: The error only happens on Mac machines, not on PC)

That’s actually quite unlikely but may be possible. Field initializers run before any constructor. Apart from that this is a horrible way to define a vector font. Not only is the code painful to read, it also contains huge amount of clutter / overhead between the actual data. Apart from that organising each character in a jagged array is very inefficient as well.

I guess your outer array essentially defines an array of line strips and the inner array the actual strips? I’ve created a vector font system a long time ago. It simply used a very simple descriptive language. Essentially some sort of turtle graphic. The only commands are “C” to define an ascii character, “H” to define a hex character code, “M” to move to a certain position, “D” to draw a line to a certain position, “S” to specify the spacing / x size of a character and “F” to specify the font name and a general scaling factor. The whole thing is a text based and tokenized format that can easily be parsed. So it’s much easier to add or modify fonts. I created those two fonts by hand:
VectorFont

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace B83.Vector
{
    public enum EOperation { Move, Draw }
    public struct Operation
    {
        public EOperation type;
        public float x;
        public float y;
        public Operation(float aX, float aY, EOperation aType)
        {
            type = aType;
            x = aX;
            y = aY;
        }
    }
    public class VectorFontCharacter
    {
        public char character;
        public List<Operation> operations = new List<Operation>();
        public float xSize = 0;
        public List<Vector2> lines;
        public IEnumerable<Vector2> GetLines()
        {
            Vector2 last = Vector2.zero;
            for(int i = 0; i < operations.Count; i++)
            {
                var op = operations[i];
                if (op.type == EOperation.Move)
                {
                    last = new Vector2(op.x,op.y);
                    //yield return last;
                }
                else if (op.type == EOperation.Draw)
                {
                    yield return last;
                    last = new Vector2(op.x,op.y);
                    yield return last;
                }
            }
        }
        public static bool LineLineIntersection(out Vector2 intersection, Vector2 linePoint1, Vector2 lineVec1, Vector2 linePoint2, Vector2 lineVec2)
        {
            intersection = Vector2.zero;
            Vector2 lineVec3 = linePoint2 - linePoint1;
            float crossVec1and2 = lineVec1.x * lineVec2.y - lineVec2.x * lineVec1.y;
            float crossVec3and2 = lineVec3.x * lineVec2.y - lineVec2.x * lineVec3.y;
            float s = crossVec3and2 / crossVec1and2;
            if ((s >= 0.0f) && (s <= 1.0f))
            {
                intersection = linePoint1 + (lineVec1 * s);
                return true;
            }
            return false;
        }
        public IEnumerable<Vector2> GetOutLine(float aWidth)
        {
            List<Vector2> lines = GetLines().ToList();
            Func<Vector2, Vector2,int, List<Vector2>> clipLine = null;
            clipLine = (Vector2 A, Vector2 B, int aIndex) => {
                var res = new List<Vector2>();
                for (int n = 0; n < lines.Count / 2; n += 2)
                {
                    if (n == aIndex)
                        continue;
                    var c1 = lines[n];
                    var c2 = lines[n + 1];
                    var d = c2 - c1;
                    var no = new Vector2(d.y, -d.x).normalized * 0.5f * aWidth;
                    var intersection = Vector2.zero;
                    if (LineLineIntersection(out intersection, A, B - A, c1 + no, c2 - c1))
                    {
                        if (Vector2.Dot(B - A, no) < 0)
                            res.AddRange(clipLine(A, intersection+no*0.01f, aIndex));
                        else
                            res.AddRange(clipLine(B, intersection + no * 0.01f, aIndex));
                        if (LineLineIntersection(out intersection, A, B - A, c1 - no, c2 - c1))
                        {
                            if (Vector2.Dot(B - A, -no) < 0)
                                res.AddRange(clipLine(A, intersection - no * 0.01f, aIndex));
                            else
                                res.AddRange(clipLine(B, intersection - no * 0.01f, aIndex));
                        }
                        return res;
                    }
                }
                res.Add(A);
                res.Add(B);
                return res;
            };
            List<Vector2> result = new List<Vector2>();
            for(int i = 0; i < lines.Count/2; i+=2)
            {
                var v1 = lines[i];
                var v2 = lines[i + 1];
                var dir = v2 - v1;
                var normal = new Vector2(dir.y, -dir.x).normalized * 0.5f*aWidth;
                result.AddRange(clipLine(v1 + normal, v2 + normal, i));
                result.AddRange(clipLine(v1 - normal, v2 - normal, i));
            }
            return result;
        }
    }

    public class VectorFont
    {
        public static VectorFont DefaultFont = Deserialize(
             "CA;M0;0;D50;100;D100;0;M30;60;D70;60;"+
             "CB;M0;0;D0;100;D25;100;D50;75;D0;50;D25;50;D50;25;D25;0;D0;0;" +
             "CC;M75;25;D50;0;D25;0;D0;25;D0;75;D25;100;D50;100;D75;75;" +
             "CD;M0;0;D0;100;D25;100;D50;75;D50;25;D25;0;D0;0;" +
             "CE;M75;100;D0;100;D0;0;D75;0;M0;50;D50;50;" +
             "CF;M75;100;D0;100;D0;0;M0;50;D50;50;" +
             "CG;M75;75;D50;100;D25;100;D0;75;D0;25;D25;0;D50;0;D75;25;D75;50;D50;50;" +
             "CH;M0;0;D0;100;M75;0;D75;100;M0;50;D75;50;" +
             "CI;M37;0;D37;100;" +
             "CJ;M0;100;D75;100;D75;25;D50;0;D25;0;D0;25;D0;50;" +
             "CK;M0;0;D0;100;M75;0;D0;50;D75;100;" +
             "CL;M0;100;D0;0;D75;0;" +
             "CM;M0;0;D0;100;D37;50;D75;100;D75;0;" +
             "CN;M0;0;D0;100;D75;0;D75;100;" +
             "CO;M75;25;D50;0;D25;0;D0;25;D0;75;D25;100;D50;100;D75;75;D75;25;" +
             "CP;M0;0;D0;100;D50;100;D75;75;D75;50;D50;25;D0;25;" +
             "CQ;M75;25;D50;0;D25;0;D0;25;D0;75;D25;100;D50;100;D75;75;D75;25;M75;0;D50;25;" +
             "CR;M0;0;D0;100;D50;100;D75;75;D50;50;D0;50;D75;0;" +
             "CS;M75;75;D50;100;D25;100;D0;75;D75;25;D50;0;D25;0;D0;25;" +
             "CT;M37;0;D37;100;M0;100;D75;100;" +
             "CU;M0;100;D0;25;D25;0;D50;0;D75;25;D75;100;" +
             "CV;M0;100;D37;0;D75;100;" +
             "CW;M0;100;D12;0;D37;50;D63;0;D75;100;" +
             "CX;M0;0;D75;100;M75;0;D0;100;" +
             "CY;M0;100;D37;50;D75;100;M37;50;D37;0;" +
             "CZ;M0;100;D75;100;D0;0;D75;0;" +
             "C0;M0;0;D0;100;D50;100;D50;0;D0;0;" +
             "C1;M50;100;D50;0;" +
             "C2;M0;100;D50;100;D50;50;D0;50;D0;0;D50;0;" +
             "C3;M0;100;D50;100;D50;0;D0;0;M0;50;D50;50;" +
             "C4;M50;100;D50;0;M0;100;D0;50;D50;50;" +
             "C5;M50;100;D0;100;D0;50;D50;50;D50;0;D0;0;" +
             "C6;M50;100;D0;100;D0;0;D50;0;D50;50;D0;50;" +
             "C7;M0;100;D50;100;D50;0;" +
             "C8M0;0;D0;100;D50;100;D50;0;D0;0;M0;50;D50;50;"+
             "C9M0;0;D50;0;D50;100;D0;100;D0;50;D50;50;"+
             "C-;M25;50;D75;50;" +
             "C,;M0;0;D0;10;" +
             "C.;M20;20;D20;10;D30;10;D30;20;D20;20;" +
             "Ca;M0;75;D25;100;D50;100;D75;75;D75;0;M75;25;D50;0;D25;0;D0;25;D0;50;D25;75;D50;75;D75;50;" +
             "Cb;M0;0;D0;100;M0;25;D25;0;D50;0;D75;25;D75;50;D50;75;D25;75;D0;50;" +
             "Cc;M75;25;D50;0;D25;0;D0;25;D0;50;D25;75;D50;75;D75;50;" +
             "Cd;M75;0;D75;100;M75;25;D50;0;D25;0;D0;25;D0;50;D25;75;D50;75;D75;50;" +
             "Ce;M0;37;D75;37;D75;50;D50;75;D25;75;D0;50;D0;25;D25;0;D75;0;" +
             "Cf;;M0;0;D0;75;D25;100;D50;100;M0;50;D25;50;" +
             "Cg;M75;25;D50;0;D25;0;D0;25;D0;50;D25;75;D50;75;D75;50;D75;0;D50;-25;D25;-25;D0;0;" +
             "Ch;M0;0;D0;100;M0;50;D25;75;D50;75;D75;50;D75;0;" +
             "Ci;M25;100;D25;88;M25;75;D25;0;" +
             "Cj;M25;100;D25;88;M25;75;D25;0;D0;-25;" +
             "Ck;M0;0;D0;100;M50;75;D0;50;D50;0;" +
             "Cl;M25;0;D25;100;" +
             "Cm;M0;0;D0;75;M0;50;D25;75;D50;50;D50;0;M50;50;D75;75;D100;50;D100;0;" +
             "Cn;M0;0;D0;75;M0;50;D25;75;D50;75;D75;50;D75;0;" +
             "Co;M75;25;D50;0;D25;0;D0;25;D0;50;D25;75;D50;75;D75;50;D75;25;" +
             "Cp;M0;-25;D0;75;D25;75;D50;50;D25;25;D0;25;" +
             "Cq;M50;25;D25;25;D0;50;D25;75;D50;75;D50;-25;" +
             "Cr;M0;0;D0;75;M0;50;D25;75;D50;75;" +
             "Cs;M0;0;D25;0;D50;25;D0;50;D25;75;D50;75;" +
             "Ct;M25;0;D25;100;M0;75;D50;75;" +
             "Cu;M0;75;D0;25;D25;0;D50;0;D75;25;D75;75;" +
             "Cv;M0;75;D30;0;D60;75;" +
             "Cw;M75;75;D60;0;D45;50;D30;0;D15;75;" +
             "Cx;M0;0;D75;75;M0;75;D75;0;" +
             "Cy;M0;75;D0;50;D25;25;D50;25;D75;50;D75;75;M50;25;D0;-25;" +
             "Cz;M0;75;D75;75;D0;0;D75;0;" +
             "C:;M40;70;D50;70;D50;60;D40;60;D40;70;M40;40;D50;40;D50;30;D40;30;D40;40;" +
             "C(;M30;0;D10;20;D10;80;D30;100;" +
             "C);M10;0;D30;20;D30;80;D10;100;" +
             "C[;M30;100;D10;100;D10;0;D30;0;" +
             "C];M10;0;D30;0;D30;100;D10;100;" +
             "C/;M10;10;D40;90;" +
             "C\\;M10;90;D40;10;"
            );
        public static VectorFont AutoTraxFont = Deserialize(
            "FAutoTraxFont;0.04166666666;"+
            "CA;S16;M0;0;D0;8;D8;24;D16;8;D16;0;M0;8;D16;8;" +
            "CB;S16;M0;0;D12;0;D16;4;D16;8;D12;12;D16;16;D16;20;D12;24;D0;24;M4;0;D4;24;M12;12;D4;12;" +
            "CC;S16;M16;4;D12;0;D4;0;D0;4;D0;20;D4;24;D12;24;D16;20;" +
            "CD;S16;M0;0;D12;0;D16;4;D16;20;D12;24;D0;24;M4;0;D4;24;" +
            "CE;S16;M16;0;D0;0;D0;24;D16;24;M0;12;D8;12;" +
            "CF;S16;M0;0;D0;24;D16;24;M0;12;D8;12;" +
            "CG;S16;M12;12;D16;12;D16;4;D12;0;D4;0;D0;4;D0;20;D4;24;D16;24;" +
            "CH;S16;M0;0;D0;24;M0;12;D16;12;M16;24;D16;0;" +
            "CI;S8;M0;0;D8;0;M4;0;D4;24;M0;24;D8;24;" +
            "CJ;S16;M0;4;D4;0;D12;0;D16;4;D16;24;" +
            "CK;S16;M0;0;D0;24;M0;12;D4;12;M16;24;D4;12;D16;0;" +
            "CL;S16;M16;0;D0;0;D0;24;" +
            "CM;S16;M0;0;D0;24;D8;8;D16;24;D16;0;" +
            "CN;S16;M0;0;D0;24;D16;8;M16;0;D16;24;" +
            "CO;S16;M0;0;D0;24;D16;24;D16;0;D0;0;" +
            "CP;S16;M0;0;D0;24;D12;24;D16;20;D16;16;D12;12;D0;12;" +
            "CQ;S16;M0;4;D0;20;D4;24;D12;24;D16;20;D16;8;D8;0;D4;0;D0;4;M8;8;D16;0;" +
            "CR;S16;M0;0;D0;24;D12;24;D16;20;D16;16;D12;12;D0;12;M4;12;D16;0;" +
            "CS;S16;M0;4;D4;0;D12;0;D16;4;D0;20;D4;24;D12;24;D16;20;" +
            "CT;S16;M8;0;D8;24;M0;24;D16;24;" +
            "CU;S16;M0;24;D0;4;D4;0;D12;0;D16;4;D16;24;" +
            "CV;S24;M0;24;D12;0;D24;24;" +
            "CW;S24;M0;24;D0;4;D4;0;D12;8;D20;0;D24;4;D24;24;" +
            "CX;S16;M0;0;D0;4;D16;20;D16;24;M0;24;D0;20;D16;4;D16;0;" +
            "CY;S16;M0;24;D0;20;D8;12;D16;20;D16;24;M8;12;D8;0;" +
            "CZ;S16;M16;0;D0;0;D0;4;D16;20;D16;24;D0;24;" +

            "Ca;S16;M16;0;D12;4;D12;12;D8;16;D4;16;D0;12;D0;4;D4;0;D8;0;D12;4;" +
            "Cb;S16;M0;0;D0;24;M0;8;D8;0;D12;0;D16;4;D16;12;D12;16;D8;16;D0;8;" +
            "Cc;S16;M16;0;D4;0;D0;4;D0;12;D4;16;D16;16;" +
            "Cd;S16;M16;0;D16;24;M16;8;D8;0;D4;0;D0;4;D0;12;D4;16;D8;16;D16;8;" +
            "Ce;S16;M12;0;D4;0;D0;4;D0;12;D4;16;D12;16;D16;12;D12;8;D0;8;" +
            "Cf;S16;M4;0;D4;20;D8;24;D12;24;D16;20;M0;12;D8;12;" +
            "Cg;S16;M16;0;D4;0;D0;4;D0;12;D4;16;D12;16;D16;12;D16;-4;D12;-8;D4;-8;D0;-4;" +
            "Ch;S16;M0;0;D0;24;M0;8;D8;16;D12;16;D16;12;D16;0;" +
            "Ci;S0;M0;0;D0;12;M0;16;D0;20;" +
            "Cj;S16;M0;4;D4;0;D12;0;D16;4;D16;16;M16;20;D16;24;" +
            "Ck;S16;M0;0;D0;24;M0;8;D8;8;M16;0;D8;8;D16;16;" +
            "Cl;S4;M4;0;D0;4;D0;24;" +
            "Cm;S16;M0;0;D0;16;M0;12;D4;16;D8;12;D12;16;D16;12;D16;0;M8;12;D8;8;" +
            "Cn;S16;M0;0;D0;16;M0;12;D4;16;D8;16;D12;12;D12;0;" +
            "Co;S16;M4;0;D12;0;D16;4;D16;12;D12;16;D4;16;D0;12;D0;4;D4;0;" +
            "Cp;S16;M0;0;D12;0;D16;4;D16;12;D12;16;D4;16;D0;12;M0;16;D0;-8;" +
            "Cq;S16;M16;0;D4;0;D0;4;D0;12;D4;16;D12;16;D16;12;M16;16;D16;-8;" +
            "Cr;S16;M0;0;D0;16;M0;8;D8;16;D12;16;D16;12;" +
            "Cs;S16;M0;0;D12;0;D16;4;D12;8;D4;8;D0;12;D4;16;D16;16;" +
            "Ct;S16;M8;24;D8;4;D12;0;D16;4;M0;16;D16;16;" +
            "Cu;S16;M0;16;D0;4;D4;0;D8;0;D16;8;M16;16;D16;0;" +
            "Cv;S16;M0;16;D8;0;D16;16;" +
            "Cw;S16;M0;16;D0;4;D4;0;D8;4;D12;0;D16;4;D16;16;M8;4;D8;8;" +
            "Cx;S16;M0;16;D16;0;M0;0;D16;16;" +
            "Cy;S16;M0;16;D0;4;D4;0;D8;0;D16;8;M16;16;D16;-4;D12;-8;D4;-8;" +
            "Cz;S16;M0;16;D16;16;D0;0;D16;0;" +

            "C0;S16;M0;4;D0;20;D4;24;D8;24;D12;20;D12;4;D8;0;D4;0;D0;4;" +
            "C1;S16;M0;0;D8;0;M4;0;D4;24;D0;20;" +
            "C2;S16;M16;0;D0;0;D0;8;D4;12;D12;12;D16;16;D16;20;D12;24;D4;24;D0;20;" +
            "C3;S16;M0;4;D4;0;D12;0;D16;4;D16;8;D12;12;D16;16;D16;20;D12;24;D4;24;D0;20;M12;12;D8;12;" +
            "C4;S16;M12;0;D12;24;D0;12;D0;8;D16;8;" +
            "C5;S16;M0;4;D4;0;D12;0;D16;4;D16;12;D12;16;D0;16;D0;24;D16;24;" +
            "C6;S16;M0;12;D12;12;D16;8;D16;4;D12;0;D4;0;D0;4;D0;16;D8;24;D12;24;" +
            "C7;S16;M0;24;D16;24;D16;20;D0;4;D0;0;" +
            "C8;S16;M12;12;D4;12;D0;16;D0;20;D4;24;D12;24;D16;20;D16;16;D12;12;D16;8;D16;4;D12;0;D4;0;D0;4;D0;8;D4;12;" +
            "C9;S16;M4;0;D8;0;D16;8;D16;20;D12;24;D4;24;D0;20;D0;16;D4;12;D16;12;" +

            "C!;S0;M0;0;D0;4;M0;8;D0;24;" +
            "C\";S16;M0;16;D0;24;M4;16;D4;24;" +
            "C$;S16;M0;4;D12;4;D16;8;D12;12;D4;12;D0;16;D4;20;D16;20;M8;0;D8;24;" +
            "C%;S16;M0;0;D0;4;D16;20;D16;24;M0;24;D4;24;D4;20;D0;20;D0;24;M16;0;D12;0;D12;4;D16;4;D16;0;" +
            "C&;S16;M16;0;D0;16;D0;20;D4;24;D8;20;D8;16;D0;8;D0;4;D4;0;D8;0;D16;8;" +
            "C/;S16;M0;0;D0;4;D16;20;D16;24;" +
            "C(;S8;M8;0;D0;8;D0;16;D8;24;" +
            "C);S8;M0;0;D8;8;D8;16;D0;24;" +
            "C=;S16;M0;8;D16;8;M0;16;D16;16;" +
            "C?;S12;M0;20;D4;24;D8;24;D12;20;D12;16;D8;12;D8;8;M8;4;D8;0;" +
            "C';S0;M0;16;D0;24;" +
            "C#;S16;M0;8;D16;8;M0;16;D16;16;M4;24;D4;0;M12;0;D12;24;" +
            "C-;S16;M0;12;D16;12;" +
            "C+;S16;M0;12;D16;12;M8;20;D8;4;" +
            "C*;S16;M0;12;D16;12;M8;20;D8;4;M0;20;D16;4;M0;4;D16;20;" +
            "C.;S0;M0;0;D0;4;" +
            "C,;S0;M0;4;D0;0;D-4;-4;" +
            "H003B;S16;M0;0;D0;8;M0;12;D0;16;" + // ";"
            "H0020;S16;" +// " " Space
            "C:;S0;M0;4;D0;8;M0;12;D0;16;" +
            "C{;S8;M8;0;D4;4;D4;8;D0;12;D4;16;D4;20;D8;24;" +
            "C[;S8;M8;0;D0;0;D0;24;D8;24;" +
            "C];S8;M0;0;D8;0;D8;24;D0;24;" +
            "C};S8;M0;0;D4;4;D4;8;D8;12;D4;16;D4;20;D0;24;" +
            "C\\;S16;M16;0;D16;4;D0;20;D0;24;" +
            "C~;S16;M0;4;D4;8;D12;8;D16;12;" +
            "C<;S12;M12;0;D0;12;D12;24;" +
            "C>;S12;M0;0;D12;12;D0;24;" +
            "C|;S0;M0;0;D0;24;"
            );



        public string Name = "Font";
        public Dictionary<char, VectorFontCharacter> m_Characters = new Dictionary<char, VectorFontCharacter>();
        public float LineHeight = 1.0f;
        public float CharSpacing = 0.333333f;
        public float ScaleFactor = 0.1f;

        public VectorFontCharacter GetChar(char aChar)
        {
            VectorFontCharacter res;
            if (m_Characters.TryGetValue(aChar, out res))
                return res;
            if (m_Characters.TryGetValue(' ', out res))
                return res;
            var v = m_Characters.Values.FirstOrDefault();
            if (v != null)
                return v;
            return null;
        }
        public IEnumerable<Vector2> GetLines(string aText)
        {
            Vector2 offset = Vector2.zero;
            if (string.IsNullOrEmpty(aText))
                yield break;
            foreach(var c in aText)
            {
                if (c == '\n')
                {
                    offset.y += LineHeight * ScaleFactor;
                    offset.x = 0;
                    continue;
                }
                var v = GetChar(c);
                foreach (var l in v.GetLines())
                    yield return l * ScaleFactor + offset;
                offset.x += CharSpacing + v.xSize * ScaleFactor;
            }
        }
        public IEnumerable<Vector2> GetOutLines(string aText,float aWidth)
        {
            Vector2 offset = Vector2.zero;
            foreach (var c in aText)
            {
                if (c == '\n')
                {
                    offset.y += 32;
                    offset.x = 0;
                    continue;
                }
                var v = GetChar(c);
                foreach (var l in v.GetOutLine(aWidth))
                    yield return l + offset;
                offset.x += 8 + v.xSize;
            }
        }


        public static VectorFont Deserialize(string aData)
        {
            var res = new VectorFont();
            int l = aData.Length;
            int i = 0;
            VectorFontCharacter current = null;
            Func<string> getToken = ()=>{
                string tmp = "";
                while(aData[i+1] != ';')
                {
                    tmp += aData[++i];
                }
                ++i;
                return tmp;
            };
            while (i < l )
            {
                float x;
                float y;
                switch (aData[i])
                {
                    case 'C':
                        current = new VectorFontCharacter();
                        current.character = aData[++i];
                        res.m_Characters.Add(current.character, current);
                        break;
                    case 'H':
                        current = new VectorFontCharacter();
                        string hex = getToken();
                        current.character = (char)int.Parse(hex, System.Globalization.NumberStyles.HexNumber);
                        res.m_Characters.Add(current.character, current);
                        break;
                    case 'M':
                        x = float.Parse(getToken());
                        y = float.Parse(getToken());
                        if (current == null)
                            continue;
                        current.operations.Add(new Operation(x, y, EOperation.Move));
                        break;
                    case 'D':
                        x = float.Parse(getToken());
                        y = float.Parse(getToken());
                        if (current == null)
                            continue;
                        current.operations.Add(new Operation(x, y, EOperation.Draw));
                        break;
                    case 'S':
                        x = float.Parse(getToken());
                        if (current == null)
                            continue;
                        current.xSize = x;
                        break;
                    case 'F':
                        res.Name = getToken();
                        res.ScaleFactor = float.Parse(getToken());
                        break;
                }
                i++;
            }
            foreach (var tmp in res.m_Characters.Values)
                tmp.lines = tmp.GetLines().ToList();
            return res;
        }
    }
}

Note this was a long time ago. Though the second font was inspired by the font used in AutoTrax which I used quite a lot back in the days ^^. The method “GetLines” of the VectorFont class essentially returns a stream of seperate line segments. I actually drew them with Unity’s GL class. But it could also be used to generate a mesh with “lines” topology. It was part of a fun little toy project that was a binary circuit simulator and the only native element was an inverter / not-gate ^^.
Circuit


It allowed you to create compound gates, store them as a “prefab” and create more complex stuff with them. Since everything was vector based you could endless zoom in and out and “shrink” the nested gates. Most simulators would blackbox such compound gates. I thought it would be funny to actually keep all the internal nested gates also visible. Though it wasn’t that great for performance. I managed to do some optimisations but in the end got lost in optimisation attempts :slight_smile: Though you could create fairly complex circuits. The circuit evaluation was essentially query based. So the evaluation started at the end and moved backwards through the chain. Each not-gate was an implicit “nor” gate with isolated inputs.

Feel free to use the code if you want. Though if you want to build your own system that’s fine. I just think that the way you specify your vector data is just a mess. It’s not structured and extremely hard to read. Also since we talk about vector graphics, you really want to avoid fractional units. You can always scale things afterwards. Using whole numbers is much easier. Just pick a grid size and a corresponding scale factor. From what I’ve seen your coordinates don’t seem to fall on any reasonable grid (except maybe a 1000th grid). So it’s up to you.

1 Like

Do you have a stack trace of when the overflow happens? I find it hard to believe that even 100s of symbols would be enough to cause a stack overflow, there usually needs to be some recursion involved.

It’s true that field initializers are executed as part of the constructor. This is also why structs can’t have field initializers, they are required to have an empty constructor and therefore also cannot initialize and fields.

Generally, on Unity-serialized types, it’s not good to initialize too much in constructors. Those objects will be recreated many times during the editor’s operation and you end up reallocating the data over and over again. It’s better to lazily allocate them only when needed.

Hi Adrian,

thank you for your answer. Concerning your question of a stack trace: I have not much logs, but this is what I have: Someone who tested it for me said that it crashes on Mac all the time, but the only information he could gave me were those:

Obtained 26 stack frames.
#0 0x0000030a406fac in DrawXXL.DrawXXL_LinesManager:.ctor () [{0x2dd73e378} + 0x2aafac] [/Users/paul.cummings/Library/Mobile Documents/com~apple~CloudDocs/Unity/code/bla/Assets/Draw XXL/scripts/components/internal utilities/DrawXXL_LinesManager.cs :: 1636u] (0x30a15c000 0x30a4bff20) [0x298802a80 - Unity Child Domain]
#1 0x000002d9e86c48 in (wrapper runtime-invoke) object:runtime_invoke_void__this__ (object,intptr,intptr,intptr) [{0x2b4a2d280} + 0x70] (0x2d9e86bd8 0x2d9e86ce4) [0x298802a80 - Unity Child Domain]
#2 0x0000029bddd844 in mono_jit_runtime_invoke
#3 0x0000029bf63650 in do_runtime_invoke
#4 0x0000029bf63570 in mono_runtime_invoke
#5 0x0000010198209c in mono_runtime_invoke_profiled(MonoMethod*, ScriptingObjectPtr, void**, MonoException**, MonoClass*)
#6 0x00000101981f28 in mono_runtime_object_init_exception(ScriptingObjectPtr, MonoException**)
#7 0x000001019e2cb8 in scripting_unity_engine_object_new_and_invoke_default_constructor(ScriptingClassPtr, Object*, ScriptingExceptionPtr*)
#8 0x00000101a65b78 in SerializableManagedRef::RebuildMonoInstance(Object*, ScriptingClassPtr, ScriptingObjectPtr, MonoScript*)
#9 0x00000101a7a5cc in PersistentManager::ProduceObject(SerializedFile&, SerializedObjectIdentifier, int, ObjectCreationMode, PersistentManager::LockFlags)
#10 0x00000101a7a0e0 in PersistentManager::CreateThreadActivationQueueEntry(SerializedFile&, SerializedObjectIdentifier, int, bool, PersistentManager::LockFlags)
#11 0x00000101a7383c in PersistentManager::PreallocateObjectThreaded(int, PersistentManager::LockFlags)
#12 0x00000101a73b7c in PersistentManager::LocalSerializedObjectIdentifierToInstanceID(int, LocalSerializedObjectIdentifier const&, int&, PersistentManager::LockFlags)
#13 0x000001013309d0 in void ImmediatePtr<Unity::Component>::Transfer<StreamedBinaryRead>(StreamedBinaryRead&)
#14 0x00000101330b60 in void StreamedBinaryRead::TransferSTLStyleArray<dynamic_array<GameObject::ComponentPair, 0ul> >(dynamic_array<GameObject::ComponentPair, 0ul>&, TransferMetaFlags)
#15 0x00000101327b78 in void GameObject::Transfer<StreamedBinaryRead>(StreamedBinaryRead&)
#16 0x000001013260dc in GameObject::VirtualRedirectTransfer(StreamedBinaryRead&)
#17 0x00000101a966b8 in SerializedFile::ReadObject(long long, ObjectCreationMode, bool, TypeTree const**, bool*, Object&)
#18 0x00000101a7aa30 in PersistentManager::ReadAndActivateObjectThreaded(int, SerializedObjectIdentifier const&, SerializedFile*, bool, bool, PersistentManager::LockFlags)
#19 0x00000101a7b544 in PersistentManager::LoadFileCompletelyThreaded(core::basic_string_ref<char>, long long*, int*, int, PersistentManager::LoadFlags, LoadProgress&, PersistentManager::LockFlags)
#20 0x0000010169fe90 in LoadSceneOperation::Perform()
#21 0x000001016a3eac in PreloadManager::ProcessSingleOperation()
#22 0x000001016a3b1c in PreloadManager::Run()
#23 0x000001016a3a8c in PreloadManager::Run(void*)
#24 0x0000010182d6a8 in Thread::RunThreadWrapper(void*)
#25 0x0000019292826c in _pthread_start

The script that contains the symbol definition fields is called „DrawXXL.DrawXXL_LinesManager“. It is mentioned right on top of the stack trace. There is also the number „1636u“ mentioned which I assume is the line in the script that makes the problems. This line points to one of the symbol definition fields in the script.

Then I tested it myself on Mac and the Unity Editor froze on entering playmode, so I could not look at the console for logs. Though I looked into the Editor log file and saw this:

9004327--1240732--stackOverflowException_DXXL_kl.JPG

There it mentions the StackOverflowException produced by „DrawXXL.DrawXXL_LinesManager“, unfortunately without script line number.

Do you see more information than I when looking on those logs?

Does this class actually have a constructor? If so, what does it look like?

It’s very unlikely that the field initializers would cause a stackoverflow. First of all even when you have, say20 vectors per “symbol”. Each vector consists of 2 floats and each float occupies 4 bytes. That is 16k of memory. The default stacksize in .NET / C# is usually 1MB. So that’s only around 1% of the stack size. Even when you assume that the initialization code is inefficient and we double the amount so all the literal values could have their own place in memory, it would still be not that much.

So if the class has a constructor, I would look for a problem in there.

Hm, that’s strange. There doesn’t seem to be any recursion and I doubt you could really allocate so much you run out of stack space. Note that classes, including arrays, are allocated on the heap, so you should only have a few pointers on the stack. Maybe Mono is temporarily storing some things or the stack or it somehow allocates way more than it needs? Looking at the IL could give you a hint as to how much the stack is even involved in allocating the arrays.

I’d still try to lazily create the data only when needed. Then you could only do this per character, which would avoid allocating all characters at once and maybe also circumvents the crash.

You could also try reporting it to Unity. It does seem strange to cause a stack overflow but it’s also hard to tell from the outside what is going on in Mono and what the stack size limits even are on the different platforms.

Hi Bunny83,

thank you for your answers and information.

The class is a MonoBehaviour so I don’t have access to the constructor. I agree with your calculation that the vector arrays by far don’t take the memory that the stacksize offers. It would be very very inefficient initialization code. But then again on the other hand: What else is contained in a MonoBehaviour constructor other than field initializers that could add memory usage?

Another point why my guess goes to the vector array field initializers: I had the same error a while ago on Windows machines. Back then I declared the vector arrays as static fields in a non-MonoBehaviour class (but also without explicitly defined constructor). It was a similar hard-to-trace-situation. The error disappeared when I changed the vector arrays to be declared as non-static fields in the MonoBehaviour. But unfortunately this fixed it only for Windows machines, and now Macs have the problem.

Stack memory is not allocated like normal memory. The stack frame for a method call is allocated the moment you enter the method and this is done by simply offsetting the stack pointer. That’s all. Also the arrays do not live on the stack at all, like @Adrian already mentioned. However immediate values that are assigned may temporarily be stored in local variables on the stack. The same could be true for the immediate float values you pass to all the Vector2 constructors. All your Vector2 values also need to implicitly be converted to Vector3 values. So they may be stored twice. Though as already said that shouldn’t be that much of an issue.

In most cases a StackOverflow happens because of a too deep nesting of method calls, usually due to a recursive method call. You don’t have some kind of self reference and create an instance of the monobehaviour in a field initializer with “new”, do you?

Note that the initialization of an object is a quite complex topic. Field initializers are not part or inside the constructor method (.ctor). They are initialized before the actual constructor is called. See this SO answer. Since the SO exception is thrown by or from the constructor itself, I would guess that either:

  • There’s something in the actual constructor that is causing the SO
  • Or the stack was already at its limit and that ordinary method call just made it tip over.

The second case is usually the actual case. Even when you have a recursive method call, the SO exception may be thrown inside some other method that may get called from inside the recursive method. So for example a recursive chain has just reached the stack limit, so your method can still run but when you call any other method, say Mathf.Sin the stack overflows because there’s no enough room to store the return address. That’s why stack traces are usually interesting as they reveal the actual call chain. In your case however we can’t really tell what may be the reason why the stack overflows. There could be plenty of reasons which may not even be related to the constructor in question. If you have some faulty native method call with the wrong calling convention it’s possible to build up stack frames which will ultimatively cause an SO at some point, somewhere.

1 Like

I went the route of lazy initialization per character and that indeed fixed the crashes. Thank you for pointing me in this direction.