GithubHelp home page GithubHelp logo

Comments (18)

DarthAffe avatar DarthAffe commented on June 12, 2024

I just run a bit of profiling myself but I'm pretty sure this is a problem in your code.
It seems like you're leaking groups for a reason i just realized isn't documented right now.

If you create a group it's attached to the device by default (that's what the bool parameter is for). But you never detach them so they stack up over time. You can check this in the debugger by breaking in the update method after some runs and checking the "LedGroups"-Property.

If you can confirm this is the problem, there are different ways to solve it. Personally I prefer to only create as much groups/brushes as I need and modify the keys/values inside to get the result I want. But you could of course also create always new groups. Just make sure the old (unused) ones get detached by calling

myLedGroup.Detach(); // using CUE.NET.Groups.Extensions;
// or
CueSDK.KeyboardSDK.DetachLedGroup(myLedGroup);

from cue.net.

roxaskeyheart avatar roxaskeyheart commented on June 12, 2024

Oh that would certainly explain the memory leak then, thanks for making me aware of this.
So I took the ListLedGroup's out of the function scope to access them from the CorsairUpdateLED() function.

The code now looks like this:

    private ListLedGroup _CorsairAllKeyboardLED = new ListLedGroup(CueSDK.KeyboardSDK, CueSDK.KeyboardSDK);
    private ListLedGroup _CorsairAllMouseLED = new ListLedGroup(CueSDK.MouseSDK, CueSDK.MouseSDK);
    private ListLedGroup _CorsairAllHeadsetLED = new ListLedGroup(CueSDK.HeadsetSDK, CueSDK.HeadsetSDK);
    private ListLedGroup _CorsairAllMousepadLED = new ListLedGroup(CueSDK.MousematSDK, CueSDK.MousematSDK);
    ListLedGroup _CorsairKeyboardIndvLED;
    ListLedGroup _CorsairMouseIndvLED;
    ListLedGroup _CorsairMousepadIndvLED;

    public void CorsairUpdateLED()
    {
        if (CorsairSDK != false)
        {
            if (CorsairDeviceHeadset == true) { CueSDK.HeadsetSDK.Update(); }
            if (CorsairDeviceKeyboard == true) { CueSDK.KeyboardSDK.Update(); }
            if (CorsairDeviceMouse == true) { CueSDK.MouseSDK.Update(); }
            if (CorsairDeviceMousepad == true) { CueSDK.MousematSDK.Update(); }

            _CorsairAllKeyboardLED.Detach();
            _CorsairAllMouseLED.Detach();
            _CorsairAllHeadsetLED.Detach();
            _CorsairAllMousepadLED.Detach();
            
            _CorsairKeyboardIndvLED.Detach();
            _CorsairMouseIndvLED.Detach();
            _CorsairMousepadIndvLED.Detach();
        }
    }

//Update a specific LED on the keyboard
public void CorsairApplyMapKeyLighting(string key, System.Drawing.Color col)
{
    if (CorsairSDK != false)
    {
        //Send Lighting
        if (CorsairDeviceKeyboard == true)
        {
            try
            {
                if (Corsairkeyids.ContainsKey(key))
                {
                    _CorsairKeyboardIndvLED = new ListLedGroup(CueSDK.KeyboardSDK);
                    SolidColorBrush _CorsairLEDBrush = new SolidColorBrush(col);
                    _CorsairKeyboardIndvLED.AddLed(Corsairkeyids[key]);
                    _CorsairKeyboardIndvLED.ZIndex = 10;
                    _CorsairKeyboardIndvLED.Brush = _CorsairLEDBrush;
                }
            }
            catch (Exception ex) {  }
        }
    }
}

However I still am getting the same memory issues it seems, with pretty much no notable change?

Basically what I'm doing is trying to set 1 key at a time to a specific color using the CorsairApplyMapKeyLighting() function, is there a better way to do this than creating a new LED group and color brush every time it is called?

from cue.net.

DarthAffe avatar DarthAffe commented on June 12, 2024

I think you should not create new groups if possible - this makes things quite complex and hard for me to understand without the whole context.
If I run a this program (it should somehow simulate the behavior you want to achieve) I don't see any memory-leaks.

    public class Program
    {
        private static ListLedGroup _singleKeyLedGroup;
        private static Random _random = new Random();

        public static void Main(string[] args)
        {
            CueSDK.Initialize();

            CueSDK.KeyboardSDK.Brush = (SolidColorBrush)Color.Black;

            _singleKeyLedGroup = new ListLedGroup(CueSDK.KeyboardSDK);

            // Just do something for a while ~
            for (int i = 0; i < 1000; i++)
            {
                CorsairApplyMapKeyLighting(GetRandomLedId(), ColorHelper.ColorFromHSV(_random.Next(360), 1, 1));
                CueSDK.KeyboardSDK.Update();

                Thread.Sleep(60);
            }
        }

        private static void CorsairApplyMapKeyLighting(CorsairLedId key, Color col) // I replaced the string since I don't know what logic you're using to get the key but that shouldn't mather.
        {
            _singleKeyLedGroup.RemoveLeds(_singleKeyLedGroup.GetLeds().ToList()); // It's a bit sad that there is no 'clear'-methodon the group, but this will do the same even if it is some kind of workaround
            _singleKeyLedGroup.AddLed(key);
            _singleKeyLedGroup.Brush = (SolidColorBrush)col; // this could be replaced too if you keep a reference to the brush but it's not really needed.
        }

        private static CorsairLedId GetRandomLedId()
        {
            CorsairLedId[] ids = ((CorsairLedId[])Enum.GetValues(typeof(CorsairLedId)));
            return ids[_random.Next(ids.Length - 1)];
        }
    }

from cue.net.

roxaskeyheart avatar roxaskeyheart commented on June 12, 2024

Thanks for helping me out with these issues, I really appreciate it! >.<;

Okay so I see what you're trying to achieve here, and have attempted to replicate most of it in my code. One problem I can see however is in the logic of how I call the Update(); method.

Basically I have a separate async function which handles all the calls to these functions. This primary function runs every 500 or so milliseconds and essentially calls the relevant functions to draw across the keyboard. The order of this function more or less follows this pattern:

  1. Sets a base colour across all of the keys (this gets skipped if nothing has changed from the past iteration) - uses CorsairUpdateAll() function.

  2. Sets individual colours on specific keys, using multiple CorsairApplyMapKeyLighting() calls, over the base layer (was utilising the z-index of the LED group).

  3. Calls the CueSDK.KeyboardSDK.Update() method.

  4. Sleeps for 500ms.

  5. Repeats

When I try to implement your code, nothing gets set on step 2. I'm assuming this is because each call to this function overrides the last, before the CueSDK.KeyboardSDK.Update() method can be called.

I'm not sure how much this complicates what I'm trying to achieve. When I first started coding these functions I had the Update() method within each function, however this lead to some weird results where LED's would essentially flash on and off constantly which lead me to having a single Update() call back in the primary async function, which achieved the result I was looking for.

Hopefully that makes sense?

from cue.net.

roxaskeyheart avatar roxaskeyheart commented on June 12, 2024

Will post some sample code to better demonstrate

class TestClass
{
    bool CorsairSDK = false;
    bool Setbase = false;
    Color _BaseColor = System.Drawing.Color.Black;

    private ListLedGroup _CorsairAllKeyboardLED;
    private ListLedGroup _CorsairAllMouseLED;
    private ListLedGroup _CorsairAllHeadsetLED;
    private ListLedGroup _CorsairAllMousepadLED;

    private ListLedGroup _CorsairKeyboardIndvLED;
    private ListLedGroup _CorsairMouseIndvLED;
    private ListLedGroup _CorsairMousepadIndvLED;

    void Main()
    {
        //Split off Primary function chain to a Task 
        new Task(() => { var _call = CallPrimary(); }).Start();
    }
    
    //Async function which calls Primary
    async Task CallPrimary()
    {
        await Task.Delay(500); //Sleep for 500 ms
        Primary();
    }

    //The Primary loop function
    void Primary()
    {
        var BaseColor = System.Drawing.Color.DodgerBlue;
        var HighlightColor = System.Drawing.Color.Magenta;


        //Set Base Keyboard lighting. 
        //Other LED's are built above this base layer.
        if (Setbase == false)
        {
            _BaseColor = BaseColor;
            CorsairUpdateState(_BaseColor);
            Setbase = true;
        }

        //Highlight critical keys
        CorsairApplyMapKeyLighting("D1", HighlightColor);
        CorsairApplyMapKeyLighting("D2", HighlightColor);
        CorsairApplyMapKeyLighting("D3", HighlightColor);
        CorsairApplyMapKeyLighting("D4", HighlightColor);
        CorsairApplyMapKeyLighting("A", HighlightColor);
        CorsairApplyMapKeyLighting("B", HighlightColor);
        CorsairApplyMapKeyLighting("C", HighlightColor);


        //Update Keyboard
        if (CorsairSDK == true)
        {
            CorsairUpdateLED();
        }
    }

    //Initialise CUE.Net
    void InitializeCorsairSDK()
    {
        CueSDK.Initialize();

        CorsairSDK = true;

        CueSDK.KeyboardSDK.Brush = (SolidColorBrush)Color.Red;

        //Group for All Keys
        _CorsairAllKeyboardLED = new ListLedGroup(CueSDK.KeyboardSDK, CueSDK.KeyboardSDK);
        _CorsairAllKeyboardLED.ZIndex = 1;

        //Group for individual Keys
        _CorsairKeyboardIndvLED = new ListLedGroup(CueSDK.KeyboardSDK);
        _CorsairKeyboardIndvLED.ZIndex = 10;
    }

    //Send the update to the device
    void CorsairUpdateLED()
    {
        if (CorsairSDK != false)
        {
            CueSDK.KeyboardSDK.Update();
        }
    }

    //Update function for ALL keys
    void CorsairUpdateState(System.Drawing.Color col)
    {
        if (CorsairSDK != false)
        {
            SolidColorBrush _CorsairAllLEDBrush = new SolidColorBrush(col);
            _CorsairAllHeadsetLED.Brush = _CorsairAllLEDBrush;
        }
    }

    //Update function for Individual key
    void CorsairApplyMapKeyLighting(string key, System.Drawing.Color col)
    {
        if (CorsairSDK != false)
        {
            if (Corsairkeyids.ContainsKey(key))
            {
                SolidColorBrush _CorsairLEDBrush = new SolidColorBrush(col);

                _CorsairKeyboardIndvLED.RemoveLeds(_CorsairKeyboardIndvLED.GetLeds().ToList());
                _CorsairKeyboardIndvLED.AddLed(Corsairkeyids[key]);
                _CorsairKeyboardIndvLED.Brush = (SolidColorBrush)col;
            }
        }
    }

    //Dictionary for String -> CorsairLedID lookup
    Dictionary<string, CorsairLedId> Corsairkeyids = new Dictionary<string, CorsairLedId>()
    {
        {"F1", CorsairLedId.F1},
        {"F2", CorsairLedId.F2},
        {"F3", CorsairLedId.F3},
        {"F4", CorsairLedId.F4},
        {"F5", CorsairLedId.F5},
        {"F6", CorsairLedId.F6},
        {"F7", CorsairLedId.F7},
        {"F8", CorsairLedId.F8},
        {"F9", CorsairLedId.F9},
        {"F10", CorsairLedId.F10},
        {"F11", CorsairLedId.F11},
        {"F12", CorsairLedId.F12},
        {"D1", CorsairLedId.D1},
        {"D2", CorsairLedId.D2},
        {"D3", CorsairLedId.D3},
        {"D4", CorsairLedId.D4},
        {"D5", CorsairLedId.D5},
        {"D6", CorsairLedId.D6},
        {"D7", CorsairLedId.D7},
        {"D8", CorsairLedId.D8},
        {"D9", CorsairLedId.D9},
        {"D0", CorsairLedId.D0},
        {"A", CorsairLedId.A},
        {"B", CorsairLedId.B},
        {"C", CorsairLedId.C},
        //...
    };
}

from cue.net.

DarthAffe avatar DarthAffe commented on June 12, 2024

This should do what you described above if I understood it right:

    public class Program
    {
        private static Random _random = new Random();
        private static ListLedGroup _keyMapLedGroup;
        private static KeyMapBrush _keyMapBrush;

        public static void Main(string[] args)
        {
            Console.WriteLine("Press any key to exit ...");
            Console.WriteLine();
            Task.Factory.StartNew(
            () =>
            {
                Console.ReadKey();
                Environment.Exit(0);
            });

            CueSDK.Initialize();

            CueSDK.KeyboardSDK.Brush = (SolidColorBrush)Color.Black; // Set base-color

            _keyMapLedGroup = new ListLedGroup(CueSDK.KeyboardSDK, CueSDK.KeyboardSDK);
            _keyMapBrush = new KeyMapBrush();
            _keyMapLedGroup.Brush = _keyMapBrush;

            CueSDK.UpdateMode = UpdateMode.Continuous;


            while (true) // this simulates your own task doing the work
            {
                _keyMapBrush.ClearKeyMap();

                int keyCount = _random.Next(30);
                for (int i = 0; i < keyCount; i++)
                {
                    _keyMapBrush.CorsairApplyMapKeyLighting(GetRandomLedId(), ColorHelper.ColorFromHSV(_random.Next(360), 1, 1));
                }

                Thread.Sleep(500);
            }
        }

        private static CorsairLedId GetRandomLedId()
        {
            CorsairLedId[] ids = ((CorsairLedId[])Enum.GetValues(typeof(CorsairLedId)));
            return ids[_random.Next(ids.Length - 1)];
        }
    }

    public class KeyMapBrush : AbstractBrush
    {
        private Dictionary<CorsairLedId, Color> _keyMap = new Dictionary<CorsairLedId, Color>();

        public void ClearKeyMap()
        {
            _keyMap.Clear();
        }

        // depending on how you call this method you might need to lock the dictionary in all the methods to prevent weird exceptions due to concurrent accesses from different threads.
        public void CorsairApplyMapKeyLighting(CorsairLedId key, Color col)
        {
            _keyMap[key] = col;
        }

        // If you're able to include your logic which key to color here this might get easier than working with the dictionary and stuff.
        protected override CorsairColor GetColorAtPoint(RectangleF rectangle, BrushRenderTarget renderTarget)
        {
            CorsairLedId ledId = renderTarget.LedId;

            Color color;
            if (_keyMap.TryGetValue(ledId, out color))
                return color;

            return CorsairColor.Transparent; ;
        }
    }

from cue.net.

roxaskeyheart avatar roxaskeyheart commented on June 12, 2024

This looks really promising, I am in the process of trying to implement it into my code (See earlier comment as I added a snapshot of my entire code for reference).

Something that I'm a bit unsure about is the line:

CueSDK.UpdateMode = UpdateMode.Continuous;

As whenever I set this value, it throws continuous System.NullReferenceException errors which I can't track down, and blocks all further CUE calls.

from cue.net.

DarthAffe avatar DarthAffe commented on June 12, 2024

Something that I'm a bit unsure about is the line:
CueSDK.UpdateMode = UpdateMode.Continuous;

Try to replace the line with a 'CueSDK.Update();'
this shouldn't trow the same exception but might give you a better message/stacktrace.

from cue.net.

roxaskeyheart avatar roxaskeyheart commented on June 12, 2024

I'm not sure if I performed it correctly, but it seems to be breaking here:
corsairmemleak2

and it occurs when CueSDK.KeyboardSDK.Update(); gets called.

from cue.net.

DarthAffe avatar DarthAffe commented on June 12, 2024

Oh shit, brush should be null-checked here.
You set the update mode before applying the brush to the led group which causes the issue here (due to the missing null check in the library). Just make sure you completely set up all the groups/brushes before enabling the auto-update (safest should be to set the brush with the object initializer)

from cue.net.

roxaskeyheart avatar roxaskeyheart commented on June 12, 2024

My initialisation function is:

CueSDK.Initialize();
CorsairSDK = true;
_CorsairKeyboardIndvBrush = new KeyMapBrush();
_CorsairKeyboardIndvLED = new ListLedGroup(CueSDK.KeyboardSDK, CueSDK.KeyboardSDK);
_CorsairAllKeyboardLED = new ListLedGroup(CueSDK.KeyboardSDK, CueSDK.KeyboardSDK);

_CorsairAllKeyboardLED.ZIndex = 1;
_CorsairKeyboardIndvLED.ZIndex = 10;

_CorsairKeyboardIndvLED.Brush = _CorsairKeyboardIndvBrush;
CueSDK.UpdateMode = UpdateMode.Continuous;

However it is still throwing this error. Could the .CorsairApplyMapKeyLighting() method OR CorsairUpdateState() be interfering with it?

void CorsairUpdateState()
{
     CueSDK.KeyboardSDK.Brush = (SolidColorBrush)col;
}

Note: Though when i comment out that line it still throws the error.

from cue.net.

DarthAffe avatar DarthAffe commented on June 12, 2024

_CorsairAllKeyboardLED

has no brush applied.

from cue.net.

roxaskeyheart avatar roxaskeyheart commented on June 12, 2024

Oh, nice catch.

So by adding:

_CorsairAllKeyboardLED.Brush = (SolidColorBrush)System.Drawing.Color.Black;

before setting the UpdateMode, that still doesn't fix the error.. >.>

However if I set the UpdateMode to Manual, call an update from my async function and comment out the following, it does work without throwing any exceptions. Though this still leaves me unable to set the base color of the keyboard after the initialisation however, and UpdateMode.Continuous is still unhappy.

void CorsairUpdateState()
{
     //_CorsairAllKeyboardLED.Brush = (SolidColorBrush)col;
}

from cue.net.

DarthAffe avatar DarthAffe commented on June 12, 2024

Ok, I don't get this. I don't see any reason why you'd need to comment that line to make it work.
Can you post your current code again?

from cue.net.

roxaskeyheart avatar roxaskeyheart commented on June 12, 2024

After hunting out a few silly mistakes, I have got it to "work" with the following code. While it does work, it continues to throw the NullReferenceExceptions every tick and UpdateMode.Continuous does not work at all still.

class TestClass
{
    bool CorsairSDK = false;
    bool Setbase = false;
    Color _BaseColor = System.Drawing.Color.Black;

    private ListLedGroup _CorsairAllKeyboardLED;
    private ListLedGroup _CorsairKeyboardIndvLED;
    private KeyMapBrush _CorsairKeyboardIndvBrush;

    void Main()
    {
        //Split off Primary function chain to a Task 
        new Task(() => { var _call = CallPrimary(); }).Start();
    }
    
    //Async function which calls Primary
    async Task CallPrimary()
    {
        await Task.Delay(500); //Sleep for 500 ms
        Primary();
    }

    //The Primary loop function
    void Primary()
    {
        var BaseColor = System.Drawing.Color.DodgerBlue;
        var HighlightColor = System.Drawing.Color.Magenta;


        //Set Base Keyboard lighting. 
        //Other LED's are built above this base layer.
        if (Setbase == false)
        {
            _BaseColor = BaseColor;
            CorsairUpdateState(_BaseColor);
            Setbase = true;
        }

        //Highlight critical keys
        CorsairApplyMapKeyLighting("D1", HighlightColor);
        CorsairApplyMapKeyLighting("D2", HighlightColor);
        CorsairApplyMapKeyLighting("D3", HighlightColor);
        CorsairApplyMapKeyLighting("D4", HighlightColor);
        CorsairApplyMapKeyLighting("A", HighlightColor);
        CorsairApplyMapKeyLighting("B", HighlightColor);
        CorsairApplyMapKeyLighting("C", HighlightColor);


        //Update Keyboard
        if (CorsairSDK == true)
        {
            CorsairUpdateLED();
        }
    }

    //Initialise CUE.Net
    void InitializeCorsairSDK()
    {
        CueSDK.Initialize();

        CorsairSDK = true;

        _CorsairKeyboardIndvBrush = new KeyMapBrush();

        _CorsairKeyboardIndvLED = new ListLedGroup(CueSDK.KeyboardSDK, CueSDK.KeyboardSDK);
        _CorsairAllKeyboardLED = new ListLedGroup(CueSDK.KeyboardSDK, CueSDK.KeyboardSDK);

        _CorsairAllKeyboardLED.ZIndex = 1;
        _CorsairKeyboardIndvLED.ZIndex = 10;

        _CorsairKeyboardIndvLED.Brush = _CorsairKeyboardIndvBrush;
        _CorsairAllKeyboardLED.Brush = (SolidColorBrush)System.Drawing.Color.Black;

        //CueSDK.UpdateMode = UpdateMode.Continuous;
    }

    //Send the update to the device
    void CorsairUpdateLED()
    {
        if (CorsairSDK != false)
        {
            CueSDK.KeyboardSDK.Update();
        }
    }

    //Update function for ALL keys
    void CorsairUpdateState(System.Drawing.Color col)
    {
        if (CorsairSDK != false)
        {
            _CorsairAllKeyboardLED.Brush = (SolidColorBrush)col;
        }
    }

    //Update function for Individual key
    void CorsairApplyMapKeyLighting(string key, System.Drawing.Color col)
    {
        if (CorsairSDK != false)
        {
            if (Corsairkeyids.ContainsKey(key))
            {
                _CorsairKeyboardIndvBrush.CorsairApplyMapKeyLighting(Corsairkeyids[key], col);
            }
        }
    }

    //Dictionary for String -> CorsairLedID lookup
    Dictionary<string, CorsairLedId> Corsairkeyids = new Dictionary<string, CorsairLedId>()
    {
        {"F1", CorsairLedId.F1},
        {"F2", CorsairLedId.F2},
        {"F3", CorsairLedId.F3},
        {"F4", CorsairLedId.F4},
        {"F5", CorsairLedId.F5},
        {"F6", CorsairLedId.F6},
        {"F7", CorsairLedId.F7},
        {"F8", CorsairLedId.F8},
        {"F9", CorsairLedId.F9},
        {"F10", CorsairLedId.F10},
        {"F11", CorsairLedId.F11},
        {"F12", CorsairLedId.F12},
        {"D1", CorsairLedId.D1},
        {"D2", CorsairLedId.D2},
        {"D3", CorsairLedId.D3},
        {"D4", CorsairLedId.D4},
        {"D5", CorsairLedId.D5},
        {"D6", CorsairLedId.D6},
        {"D7", CorsairLedId.D7},
        {"D8", CorsairLedId.D8},
        {"D9", CorsairLedId.D9},
        {"D0", CorsairLedId.D0},
        {"A", CorsairLedId.A},
        {"B", CorsairLedId.B},
        {"C", CorsairLedId.C},
        //...
    };
}

public class KeyMapBrush : AbstractBrush
{
    private Dictionary<CorsairLedId, Color> _keyMap = new Dictionary<CorsairLedId, Color>();

    public void ClearKeyMap()
    {
        _keyMap.Clear();
    }

    // depending on how you call this method you might need to lock the dictionary in all the methods to prevent weird exceptions due to concurrent accesses from different threads.
    public void CorsairApplyMapKeyLighting(CorsairLedId key, Color col)
    {
        _keyMap[key] = col;
    }

    //CorsairApplyAllKeyLighting
    public void CorsairApplyAllKeyLighting(CorsairLed[] keys, Color col)
    {
        foreach (CorsairLed key in keys)
        {
            _keyMap[key] = col;
        }
    }

    // If you're able to include your logic which key to color here this might get easier than working with the dictionary and stuff.
    protected override CorsairColor GetColorAtPoint(RectangleF rectangle, BrushRenderTarget renderTarget)
    {
        CorsairLedId ledId = renderTarget.LedId;

        Color color;
        if (_keyMap.TryGetValue(ledId, out color))
            return color;

        return CorsairColor.Transparent;
    }
}

from cue.net.

roxaskeyheart avatar roxaskeyheart commented on June 12, 2024

I just rebuilt CUE.NET from your latest commit and now both UpdateMode.Continuous and UpdateMode.Manual are working without issue.

from cue.net.

DarthAffe avatar DarthAffe commented on June 12, 2024

Ah i got it. I really don't know why this error doesn't came up earlier ...
The device is a ledgroup on it's own. since you didn't set a brush on it you got the nullreference-exception (which is now gone due to the correct null-check).

from cue.net.

roxaskeyheart avatar roxaskeyheart commented on June 12, 2024

Awesome, that makes sense.

Thanks so much for stepping through this all with me, you have been an amazing help. :D

from cue.net.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.