Matrix Rain help (poor performance)

RastHacker

Member
Joined
Oct 22, 2019
Messages
8
Programming Experience
1-3
Hi, i learn my self to making winforms in c# with vs 2015 since my 15 yo.

And today for fun i wanna make a matrix rain with rasta colors but in winform not console application.I have finished but i've got poor performance, and maybe because i'm not very good to C# coding ;D, to explain what i've done, i just have to show you ;D

(show below post 3 ...)

thanks for all the comunity for helping ;D (not registered but using this forum since a while ^^)
 
Last edited:
C#:
private void textBox1_TextChanged(object sender, EventArgs e)

{
    try

    {
        _spawnTimer.Interval = Int32.Parse(textBox1.Text);

        textBox1.BackColor = Color.White;
    }
    catch (Exception)

    {
        textBox1.BackColor = Color.Pink;
    }
}

public void addTimerToList(Timer _timer)

{
    _timerList.Add(_timer);
}
    }

    public class Spawn

{
    private Timer mainTimer = new Timer();

    private Graphics _myGraphics;

    private List<char> _charList = new List<char>();

    public bool _onSpawn = false;

    public bool _spawned = false;

    public bool _waitTo = false;

    public bool _onDespawn = false;

    private bool _isDespawing = false;

    private bool _deSpawnFinished = false;

    public int _width;

    public int _height;

    public int _x;

    public int _startY;

    private int _endY;

    public int _y;

    public int _colorValue;

    public int _currColorValue;

    public int _waitValue;

    private int _currWaitValue;

    public int _colorStepValue;

    public int _speedInterval = 1;

    private int _maxColorValue = 0;

    private int _previousColorValue = 0;

    private char _theChooserChar;

    private int _maxHeightValue;

    private int _currRepeatCount = 0;

    private int _currLocation;// = _startY - 10;

    private int _curr2 = 0;

    private Form2 _currForm;

    public Spawn(Form2 form1)

    {
        _currForm = form1;

        _currForm.addTimerToList(mainTimer);

        _maxHeightValue = _currForm.Height;// + _height*2;//_currForm.Height/2;

        _myGraphics = Graphics.FromHwnd(_currForm.Handle);

        _charList = getCharList();

        mainTimer.Interval = _speedInterval;

        mainTimer.Tick += MainTimer_Tick;
    }

    private List<char> getCharList()

    {
        var _result = new List<char>();

        foreach (char c in Properties.Resources.charList.ToCharArray())

        {
            _result.Add(c);
        }

        return _result;
    }

    private int getIndex()

    {
        int _result = 0;

        Random _getIndexRandom = new Random();

        _result = _getIndexRandom.Next(_charList.Count - 1);

        if (_result < 0)

        {
            _result = 0;
        }

        return _result;
    }
 
Last edited:
C#:
private void MainTimer_Tick(object sender, EventArgs e)

{
    if (_previousColorValue < _currColorValue)

    {
        _maxColorValue = _currColorValue;
    }

    _previousColorValue = _currColorValue;

    if (_deSpawnFinished)

    {
        mainTimer.Stop();
    }

    if (_isDespawing)

    {
        int _pathToTrace = _endY - _startY;

        int _repeatCount = 2;

        if (_currRepeatCount < _repeatCount)

        {
            if (_currLocation < _endY)

            {
                for (int i = 1; i < _repeatCount; i += 1)

                {
                    _currLocation += (_height / 4) * 3;

                    Rectangle _main = new Rectangle(_x, _y, _width, _height);

                    Rectangle _shadow = new Rectangle(_x, _currLocation, _width + (_width + 0), _height + 0);

                    Font _mainFont = new Font("Microsoft Sans Serif", _width - 6, System.Drawing.FontStyle.Regular,

                        System.Drawing.GraphicsUnit.Point, ((byte)(0)));

                    Color _selectedColor = new Color();

                    int _currCharIndex = getIndex();

                    if (_currColorValue - ((_currForm.Height / _height) * _height) > 0)

                    {
                        _currColorValue = _currColorValue - (((_currForm.Height / _height) * _height) / 2);
                    }
                    else

                    {
                        _currColorValue = 0;
                    }

                    _selectedColor = Color.FromArgb(150, 0, _currColorValue, 0);

                    //_myGraphics.FillRectangle(Brushes.Black, _main);

                    _myGraphics.FillRectangle(new SolidBrush(Color.FromArgb(new Random().Next(100, 200), 0, 0, 0)), _shadow);

                    //_myGraphics.DrawString(_charList[_currCharIndex].ToString(), _mainFont, new SolidBrush(Color.White), _main);

                    //_myGraphics.DrawString(_charList[_currCharIndex].ToString(), _mainFont,new SolidBrush(_selectedColor), _shadow);
                }

                _curr2 += 1;
            }
            else

            {
                _currLocation = _startY - 100;

                _currRepeatCount += 1;

                _curr2 = 0;
            }
        }
        else

        {
            _deSpawnFinished = true;
        }
    }
    else

    {
        if (_y < _maxHeightValue)//+ _height*2)

        {
            _y += (_height / 4) * 3;

            Rectangle _main = new Rectangle(_x, _y, _width, _height);

            Rectangle _shadow = new Rectangle(_x, _y - _height, _width, _height);

            Font _mainFont = new Font("Microsoft Sans Serif", _width - 6, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));

            Color _selectedColor = new Color();

            int _currCharIndex = getIndex();

            if (_currColorValue - 10 > 0 && _currColorValue + 10 < 255)

            {
                _selectedColor = Color.FromArgb(0,

                    new Random().Next(_currColorValue - 10, _currColorValue + 10), 0);
            }
            else

            {
                if (_currColorValue > 100)

                {
                    _currColorValue = 255;
                }
                else

                {
                    _currColorValue = 0;
                }
            }

            _myGraphics.FillRectangle(Brushes.Black, _main);

            _myGraphics.FillRectangle(Brushes.Black, _shadow);

            _myGraphics.DrawString(_charList[_currCharIndex].ToString(), _mainFont, new SolidBrush(Color.White), _main);

            _myGraphics.DrawString(_charList[_currCharIndex].ToString(), _mainFont, new SolidBrush(_selectedColor), _shadow);
        }
        else

        {
        }

        if (_onSpawn)

        {
            if (_currColorValue < _colorValue)

            {
                _currColorValue += _colorStepValue;

                Rectangle _main = new Rectangle(_x, _y, _width, _height);

                Font _mainFont = new Font("Microsoft Sans Serif", _width - 6, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));

                _myGraphics.FillRectangle(Brushes.Black, _main);

                _myGraphics.DrawString(_theChooserChar.ToString(), _mainFont, new SolidBrush(Color.FromArgb(0, _currColorValue, 0)), _main);
            }
            else

            {
                _onSpawn = false;

                //_spawned = true;

                _waitTo = true;

                _currWaitValue = 0;
            }
        }
        else

        {
            if (_spawned)

            {
                if (_waitTo)

                {
                    if (_currWaitValue < _waitValue)

                    {
                        //Console.WriteLine("Started wait to = " + _currWaitValue + " / " + _waitValue);

                        _currWaitValue += 1;
                    }
                    else

                    {
                        //Console.WriteLine("Finished wait to");

                        _waitTo = false;

                        _endY = _y;

                        _currLocation = _startY - 100;

                        _isDespawing = true;

                        mainTimer.Interval = 1;

                        //_onDespawn = true;
                    }
                }
                else

                {
                    if (_onDespawn)

                    {
                        if (!(_currColorValue - _colorStepValue < 0))

                        {
                            _currColorValue -= _colorStepValue;

                            /*int _xVar = _currForm.Height / _height;

                            int _xFunc = _xVar * _height;

                            for (int i = 1; i < _xFunc + 1; i += 1)

                            {
                                //System.Threading.Thread.Sleep(1);

                                Rectangle _despawnShadow = new Rectangle(_x, _y + 100 - _height * i, _width, _height);

                                Font _mainFont = new Font("Microsoft Sans Serif", _width - 6, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));

                                int _colorDespawnValue = (_maxColorValue / i);

                                int _currCharIndex = getIndex();

                                if (!((_currColorValue - _colorDespawnValue) > 0))

                                {
                                    _currColorValue = 0;
                                }

                                _myGraphics.FillRectangle(Brushes.Black, _despawnShadow);

                                _myGraphics.DrawString(_charList[_currCharIndex].ToString(), _mainFont, new SolidBrush(Color.FromArgb(0, _currColorValue, 0)), _despawnShadow);
                            }*/
                        }
                        else

                        {
                            _currColorValue = 0;

                            _onSpawn = false;

                            _spawned = false;

                            _waitTo = false;

                            _onDespawn = false;
                        }
                    }
                    else

                    {
                        mainTimer.Stop();
                    }
                }
            }
            else

            {
            }
        }
    }
}

public void _startSpawn()

{
    _startY = _y;

    mainTimer.Start();
}
}
 
Some quick and dirty code to do my preferred version of digital rain:
MainForm.cs:
using System;
using System.Drawing;
using System.Windows.Forms;

namespace WinFormsDigitalRain
{
    class MainForm : Form
    {
        readonly Timer _timer = new Timer();
        readonly Scene _scene;
        readonly Font _font = new Font("Consolas", 12.0f, FontStyle.Regular);
        readonly SolidBrush[] _brush = new SolidBrush[256];

        public MainForm(bool fullScreen)
        {
            FormBorderStyle = FormBorderStyle.None;
            BackColor = Color.Black;
            DoubleBuffered = true;
            Bounds = GetScreenBounds();
            KeyPress += (o, e) => Close();
            Click += (o, e) => Close();
            Paint += (o, e) => Render(e.Graphics);

            CreateBrushes();
            _scene = new Scene(100, ClientSize, GetFontWidth(), _font.Height, _brush.Length - 1);

            _timer.Interval = 100;
            _timer.Tick += (o, e) => { _scene.Update(); Invalidate(); };
            _timer.Start();

            Rectangle GetScreenBounds()
            {
                var bounds = Screen.FromControl(this).Bounds;
                if (!fullScreen)
                {
                    bounds.Width /= 2;
                    bounds.Height /= 2;
                }
                return bounds;
            }

            int GetFontWidth()
            {
                using (var g = this.CreateGraphics())
                    return (int)g.MeasureString("W", _font).Width;
            }

            void CreateBrushes()
            {
                for (int i = 0; i < _brush.Length - 1; i++)
                    _brush[i] = new SolidBrush(Color.FromArgb(0, i, 0));
                _brush[_brush.Length - 1] = new SolidBrush(Color.White);
            }
        }

        void Render(Graphics g)
        {
            SetGraphicsToMirror();
            foreach (var glyph in _scene.Glyphs)
                g.DrawString(glyph.Char, _font, _brush[glyph.Brightness], glyph.Position.X, glyph.Position.Y);

            void SetGraphicsToMirror()
            {
                g.TranslateTransform(Bounds.Width, 0);
                g.ScaleTransform(-1, 1);
            }
        }

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm(true));
        }
    }
}

Scene.cs:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;

namespace WinFormsDigitalRain
{
    class Scene
    {
        static Random _random = new Random();

        readonly Column[] _columns;
        readonly List<Glyph> _glyphs = new List<Glyph>();
        readonly Queue<Glyph> _recyle = new Queue<Glyph>();
        int _columnCount;
        int _screenHeight;
        int _fontWidth;
        int _brightest;
        bool _updating;

        public IEnumerable<Glyph> Glyphs => _glyphs.Where(g => g.IsAlive);

        public Scene(int activeColumnCount, Size screen, int fontWidth, int fontHeight, int brightest)
        {
            _screenHeight = screen.Height;
            _fontWidth = fontWidth;
            _brightest = brightest;

            _columnCount = screen.Width / fontWidth + 1;
            _columns = Enumerable.Range(0, activeColumnCount)
                                 .Select(_ => new Column(fontHeight))
                                 .ToArray();
        }

        void CreateNewColumns()
        {
            foreach(var column in _columns.Where(c => !c.IsAlive))
                column.Initialize(_random.Next(_columnCount) * _fontWidth, _random.Next(_screenHeight));
        }

        void AgeExistingGlyphs()
        {
            foreach (var glyph in _glyphs)
            {
                if (!glyph.Update())
                    _recyle.Enqueue(glyph);
            }
        }

        void CreateGlyph(Point position)
        {
            Glyph glyph;
            if (_recyle.Count > 0)
            {
                glyph = _recyle.Dequeue();
            }
            else
            {
                glyph = new Glyph();
                _glyphs.Add(glyph);
            }
            glyph.Initialize(position, _brightest);
        }

        void CreateNewGlyphs()
        {
            foreach (var column in _columns)
            {
                if (column.Update())
                    CreateGlyph(column.Position);
            }
        }

        public void Update()
        {
            if (!_updating)
            {
                _updating = true;
                CreateNewColumns();
                AgeExistingGlyphs();
                CreateNewGlyphs();
                _updating = false;
            }
        }
    }
}

Column.cs:
using System;
using System.Drawing;

namespace WinFormsDigitalRain
{
    class Column
    {
        static Random _random = new Random();

        int _maxY = 0;
        Point _position;
        int _fontHeight;

        public Point Position => _position;
        public bool IsAlive => Position.Y < _maxY;

        public Column(int fontHeight)
            => _fontHeight = fontHeight;

        public void Initialize(int x, int maxY)
        {
            _position = new Point(x, 0);
            _maxY = maxY;
        }

        public bool Update()
        {
            if (_random.Next(100) < 33)
                return false;

            _position.Y += _fontHeight;
            return true;
        }
    }
}


Glyph.cs:
using System;
using System.Collections.Generic;
using System.Drawing;

namespace WinFormsDigitalRain
{
    class Glyph
    {
        static Random _random = new Random();
        static List<string> _pool = new List<string>();
        
        static Glyph()
        {
            AddCharRangeToPool(0x0020, 0x007F); // ASCII
//            AddCharRangeToPool(0x00A0, 0x024F); // Latin-1, Latin Extended-A, Latin Extended-B
//            AddCharRangeToPool(0x0370, 0x03FF); // Greek
//            AddCharRangeToPool(0x0400, 0x04FF); // Cyrillic
            AddCharRangeToPool(0xFF61, 0xFF9F); // Halfwidth Katakana
        }

        static void AddCharRangeToPool(int low, int high)
        {
            for (char ch = (char)low; ch < (char)high; ch++)
            {
                if (!char.IsWhiteSpace(ch))
                    _pool.Add(ch.ToString());
            }
        }

        public string Char { get; private set; } = String.Empty;
        public Point Position { get; private set; }
        public int Brightness { get; private set; } = 0;

        public bool IsAlive => Brightness > 0;

        public void Initialize(Point position, int brightest)
        {
            Position = position;
            Brightness = brightest;
            RandomizeGlyph();
        }

        void RandomizeGlyph()
            => Char = _pool[_random.Next(_pool.Count)];

        public bool Update()
        {
            if (_random.Next(100) < 5)
                RandomizeGlyph();

            Brightness -= 10;
            return IsAlive;
        }
    }
}

As I said, the code above is quick and dirty. There are still lots of embedded magic numbers/strings which should be moved into the app.config.

I'm not experiencing any lagging from the code above until I start doing more than 3000 active columns at the same time. And that's on an old PC built in 2012. On my newer laptop I can go up even higher. From my analysis, the lagging I'm getting is the sheer number of glyphs that need to be updated and drawn, and not due to garbage collection pauses. Of course, with those many columns, the display is not as aethetistically pleasing anymore.
 
Back
Top Bottom