librastack / unitymvvmtoolkit Goto Github PK
View Code? Open in Web Editor NEWBrings data-binding to your Unity project
License: MIT License
Brings data-binding to your Unity project
License: MIT License
I've made a BindableListView that has some items that should get added or removed. When trying to remove item from list or calling Clrear on collection, I get ArgumentOutOfRangeException. My ViewModel class:
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using ArPaint.Services.Draw;
using ArPaint.UI.Systems.Stack;
using ArPaint.UI.Views.DrawingInfo;
using ArPaint.Utils;
using UnityMvvmToolkit.Core;
using UnityMvvmToolkit.Core.Attributes;
using UnityMvvmToolkit.Core.Interfaces;
namespace ArPaint.UI.ViewModels.Home
{
public class HomeViewModel : ViewModel
{
private readonly IDrawingsProvider _drawingsProvider;
[Observable(nameof(Drawings))]
private readonly IProperty<ObservableCollection<DrawingViewModel>> _drawings;
public ObservableCollection<DrawingViewModel> Drawings => _drawings.Value;
public ICommand CreateDrawingCommand { get; }
public HomeViewModel(IDrawingsProvider drawingsProvider)
{
_drawingsProvider = drawingsProvider;
CreateDrawingCommand = new Command(CreateDrawing);
_drawings = new Property<ObservableCollection<DrawingViewModel>>(new ObservableCollection<DrawingViewModel>());
_drawingsProvider.Drawings.CollectionChanged += OnDrawingsChanged;
BuildDrawingsCollection();
}
private void OnDrawingsChanged(object sender, NotifyCollectionChangedEventArgs evt)
{
switch (evt.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var item in evt.NewItems)
{
if (item is DrawingData drawing)
Drawings.Add(new DrawingViewModel(drawing, SelectDrawing));
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in evt.OldItems)
{
if (item is DrawingData drawing)
Drawings.RemoveAll(viewModel => viewModel.Drawing == drawing);
}
break;
default:
break;
}
}
private void BuildDrawingsCollection()
{
foreach (var drawing in _drawingsProvider.Drawings)
{
Drawings.Add(new DrawingViewModel(drawing, SelectDrawing));
}
}
private void CreateDrawing()
{
_drawingsProvider.SelectDrawing(null);
ViewStack.PushView<DrawingInfoView>();
}
private void SelectDrawing(DrawingData drawing)
{
_drawingsProvider.SelectDrawing(drawing);
ViewStack.PushView<DrawingInfoView>();
}
}
}
To automatically provide a custom BindingContext
for child elements.
public class TestItemViewModel : ICollectionItem
{
[Observable]
private readonly IProperty<string> _name = new Property<string>();
...
}
public class TestViewModel : IBindingContext
{
[Observable]
private readonly IProperty<TestItemViewModel> _selectedItem;
}
[UxmlElement]
public partial class TestItemViewModelProvider : BindingContextProvider<TestItemViewModel>
{
}
<ui:UXML ...>
<BindableUIElements.TestItemViewModelProvider binding-context-path="SelectedItem">
<UnityMvvmToolkit.UITK.BindableUIElements.BindableLabel binding-text-path="Name" />
</BindableUIElements.TestItemViewModelProvider>
</ui:UXML>
Simple declaration of BindingContextProvider
<UnityMvvmToolkit.UITK.BindableUIElements.BindingContextProvider name="TestName"/>
Throws an exception in runtime when BindingContextProvider is created. As far as I was able to trace the problem - method Deserialize of BindingContextProvider receives a visualElement argument which is being cast to BindingContextProvider inside .As method extension, which throws an exception. Received argument has a type of VisualElement.
Stacktrace (truncated my invocation entry-point):
InvalidCastException: Specified cast is not valid.
(wrapper castclass) System.Object.__castclass_with_cache(object,intptr,intptr)
UnityMvvmToolkit.UITK.Extensions.VisualElementExtensions.As[T] (System.Object visualElement) (at ./Library/PackageCache/[email protected]/Runtime/UITK/Extensions/VisualElementExtensions.Uxml.cs:12)
UnityMvvmToolkit.UITK.BindableUIElements.BindingContextProvider1+UxmlSerializedData[TBindingContext].Deserialize (System.Object visualElement) (at ./Library/PackageCache/[email protected]/Runtime/UITK/BindableUIElements/Uxmls/BindingContextProvider.T.Uxml.cs:23) UnityEngine.UIElements.VisualElementAsset.Instantiate (UnityEngine.UIElements.CreationContext cc) (at <844b3559a9bf433db5c9639bc6cc5f04>:0) UnityEngine.UIElements.VisualTreeAsset.Create (UnityEngine.UIElements.VisualElementAsset asset, UnityEngine.UIElements.CreationContext ctx) (at <844b3559a9bf433db5c9639bc6cc5f04>:0) UnityEngine.UIElements.VisualTreeAsset.CloneSetupRecursively (UnityEngine.UIElements.VisualElementAsset root, System.Collections.Generic.Dictionary
2[TKey,TValue] idToChildren, UnityEngine.UIElements.CreationContext context) (at <844b3559a9bf433db5c9639bc6cc5f04>:0)
UnityEngine.UIElements.VisualTreeAsset.CloneSetupRecursively (UnityEngine.UIElements.VisualElementAsset root, System.Collections.Generic.Dictionary`2[TKey,TValue] idToChildren, UnityEngine.UIElements.CreationContext context) (at <844b3559a9bf433db5c9639bc6cc5f04>:0)
UnityEngine.UIElements.VisualTreeAsset.CloneTree (UnityEngine.UIElements.VisualElement target, UnityEngine.UIElements.CreationContext cc) (at <844b3559a9bf433db5c9639bc6cc5f04>:0)
UnityEngine.UIElements.VisualTreeAsset.Instantiate () (at <844b3559a9bf433db5c9639bc6cc5f04>:0)
UnityEngine.UIElements.UIDocument.RecreateUI () (at <844b3559a9bf433db5c9639bc6cc5f04>:0)
UnityEngine.UIElements.UIDocument.set_visualTreeAsset (UnityEngine.UIElements.VisualTreeAsset value) (at <844b3559a9bf433db5c9639bc6cc5f04>:0)
Now the following code throws an exception. Even if the BindableTextField
is marked as read-only.
public class NameViewModel : IBindingContext
{
[Observable]
private readonly IReadOnlyProperty<string> _name = new ReadOnlyProperty<string>(nameof(NameViewModel));
}
<ui:UXML xmlns:uitk="UnityMvvmToolkit.UITK.BindableUIElements" ...>
<uitk:BindableTextField binding-value-path="Name" />
</ui:UXML>
I'd like to allow this type of binding if a BindableTextField
is read-only.
Private/protected members declared in inherited classes are not always properly retrieved.
Suppose we have following structure of ViewModels
public class BaseViewModel { }
public class MiddleViewModel : BaseViewModel
{
[Observable("MyTestInt")]
private IProperty<Int32> _myTestInt;
public MiddleClass()
{
_myTestInt = new Property<Int32>(123);
}
}
public class TopLevelViewModel : MiddleViewModel
{
}
If one were to try bind TopLevelViewModel's MyTestInt from it's parent class, then exception InvalidOperationException: Property 'MyTestInt' not found. would be raised.
Will simplify the creation of BindingContextProvider
by eliminating the need to write ParameterValueConverters
for each ViewModel.
The following will allow to provide any ViewModel for child elements.
[UxmlElement]
public partial class CommonBindingContextProvider : BindingContextProvider<IBindingContext>
{
}
Related thread #43 (comment)
ReadOnlyPropertyWrapper<TSource, TValue> doesn't have functionality to report value changes
It happens because ValueChanged event is intentionally never invoked inside ReadOnlyPropertyWrapper.
It leads to incorrect and unexpected behaviour which might be illustrated with following example
public class MyService
{
public IReadOnlyProperty<Int32> MyProperty => _myProperty;
private IProperty<Int32> _myProperty = new Property<Int32>();
}
public class MyViewModel : IBindingContext
{
[Observable("MyReadOnlyProperty")]
private IReadOnlyProperty<Int32> _myReadOnlyProperty;
public MyViewModel(MyService myService)
{
_myReadOnlyProperty = myService.MyProperty;
}
}
Suppose MyReadOnlyProperty property is bound to BindableLabel.
Now, whenever MyService internally changes property value BindableLabel doesn't receive a callback from the wrapper and is never updated
The following code leads to the Specified cast is not valid.
exception. Because of trying to cast FieldInfo
to PropertyInfo
.
public class MyViewModel : IBindingContext
{
[Observable]
private readonly ICommand<string> _command = new Command<string>(Debug.Log);
}
Make the async command IsRunning
property observable.
Will automatically notify bound UI elements of property changes.
InvalidCastException: Specified cast is not valid.
UnityMvvmToolkit.Core.Internal.ObjectWrappers.PropertyWrapper`2[TSource,TValue].SetProperty (UnityMvvmToolkit.Core.Interfaces.IBaseProperty property) (at ./Library/PackageCache/[email protected]/Runtime/Core/Internal/ObjectWrappers/PropertyWrapper.TSource.TValue.cs:56)
UnityMvvmToolkit.Core.Internal.ObjectHandlers.ObjectWrapperHandler.GetProperty[TProperty,TValueType] (UnityMvvmToolkit.Core.Interfaces.IBindingContext context, UnityMvvmToolkit.Core.BindingData bindingData, System.Reflection.MemberInfo memberInfo) (at ./Library/PackageCache/[email protected]/Runtime/Core/Internal/ObjectHandlers/ObjectWrapperHandler.cs:123)
UnityMvvmToolkit.Core.BindingContextObjectProvider.GetProperty[TProperty,TValueType] (UnityMvvmToolkit.Core.Interfaces.IBindingContext context, UnityMvvmToolkit.Core.BindingData bindingData) (at ./Library/PackageCache/[email protected]/Runtime/Core/BindingContextObjectProvider.cs:208)
UnityMvvmToolkit.Core.BindingContextObjectProvider.RentReadOnlyProperty[TValueType] (UnityMvvmToolkit.Core.Interfaces.IBindingContext context, UnityMvvmToolkit.Core.PropertyBindingData bindingData) (at ./Library/PackageCache/[email protected]/Runtime/Core/BindingContextObjectProvider.cs:132)
MVVMExtensions.BindingContextObjectProviderWithCustomTemplates.RentReadOnlyProperty[TValueType] (UnityMvvmToolkit.Core.Interfaces.IBindingContext context, UnityMvvmToolkit.Core.PropertyBindingData bindingData) (at Assets/Scripts/MVVMExtensions/BindingContextObjectProviderWithCustomTemplates.cs:79)
UnityMvvmToolkit.UITK.BindableUIElements.BindableLabel.SetBindingContext (UnityMvvmToolkit.Core.Interfaces.IBindingContext context, UnityMvvmToolkit.Core.Interfaces.IObjectProvider objectProvider) (at ./Library/PackageCache/[email protected]/Runtime/UITK/BindableUIElements/BindableLabel.cs:23)
ObjectWrapperHandler provides incorrect wrappers if normal and readonly properties are used
In Unity 2021, when an element is removed, the Unbind
method is not called.
This leads to a lot of issues because SetBindingContext
is called without resetting it.
When multiple async commands are running at the same time, cancellation only works for the last async command.
StartCommand
several timesCancelCommand
public MainViewModel()
{
StartCommand = new AsyncCommand(StartAsync).WithCancellation();
CancelCommand = new Command(() => StartCommand.Cancel());
}
public IAsyncCommand StartCommand { get; }
public ICommand CancelCommand { get; }
private async UniTask StartAsync(CancellationToken cancellationToken)
{
while (cancellationToken.IsCancellationRequested == false)
{
await UniTask.Delay(1000, cancellationToken: cancellationToken);
Debug.Log(nameof(StartAsync));
}
}
Only the last command will be canceled.
All running commands should be canceled.
It is impossible to create a converter to invert the property value because the current implementation ignores the converter when the source and target types match.
Method GetBindableChilds from VisualElementExtensions incorrectly handles cases with bindable elements in templates. Suppose there is an empty scene with 1 GameObject called View and 2 attached components (UI Document and TestCaseView). If one would run such scene and click THE BUTTON he would see a "Normal button pressed" in the console, however if he clicks on a pink background there is no "Background button pressed" in the console.
Listing of the files is provided below.
WindowContainer.uxml:
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<ui:VisualElement name="window-root" style="flex-grow: 1;">
<UnityMvvmToolkit.UITK.BindableUIElements.BindableButton command="BackgroundButtonCommand" name="window-closer" class="0; 0; 0;" style="position: absolute; top: 0; left: 0; bottom: 0; right: 0; background-color: rgb(236, 166, 166);" />
<ui:VisualElement name="content-container" contentContainer="true" picking-mode="Ignore" style="flex-grow: 1; align-items: center; justify-content: space-around;" />
</ui:VisualElement>
</ui:UXML>
TestCaseWindow.uxml:
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<ui:Template name="WindowContainer" src="project://database/Assets/TestCase/WindowContainer.uxml?fileID=9197481963319205126&guid=54cc8b8584dd8494a83f70ca4058d5b4&type=3#WindowContainer" />
<ui:Instance template="WindowContainer" style="flex-grow: 1;">
<UnityMvvmToolkit.UITK.BindableUIElements.BindableButton command="NormalButtonCommand" text="THE BUTTON" name="ok-button" style="width: 300px; height: 200px; font-size: 50px;" />
</ui:Instance>
</ui:UXML>
TestCaseViewModel.cs:
public class TestCaseViewModel : IBindingContext
{
public ICommand NormalButtonCommand { get; }
public ICommand BackgroundButtonCommand { get; }
public TestCaseViewModel()
{
NormalButtonCommand = new Command(() => Debug.Log("Normal button pressed"));
BackgroundButtonCommand = new Command(() => Debug.Log("Background button pressed"));
}
}
TestCaseView.cs:
public class TestCaseView : DocumentView<TestCaseViewModel>
{
}
Project becomes unbuildable, if UnityMVVMToolkit is imported to one without a TMPro plug-in. That happens due to default implementation of UnityUI bindable controls provided with the MVVM plugin.
Private fields are not ignored when building the ViewModel.
public class MainViewModel : IBindingContext
{
private readonly IProperty<int> _count = new Property<int>();
public IReadOnlyProperty<int> Count => _count;
}
Exception: "ArgumentException: An item with the same key has already been added." will be thrown.
The _count
field should be ignored if it is private or not marked with the Observable
attribute.
BindableListView doesn't work in the UI Toolkit.
Plus, there isn't such custom control in UI builder
BindableListView is working
View:
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements"
xmlns:uitk="UnityMvvmToolkit.UITK"
editor-extension-mode="False">
<Style
src="project://database/Assets/Art/UI/Character/Inventory.uss" />
<ui:VisualElement name="Inventory" class="inventory" contentContainer="true">
<uitk:BindableListView binding-items-source-path="Items" />
</ui:VisualElement>
</ui:UXML>
Exception in Unity debug console:
Element 'UnityMvvmToolkit.UITK.BindableUIElements.BindableListView' has no registered factory method.
UnityEngine.UIElements.UIDocument:OnValidate () (at /Users/bokken/build/output/unity/unity/ModuleOverrides/com.unity.ui/Core/GameObjects/UIDocument.cs:708)
Wouldn't it be cool when you don't need the IProperty Interface manually. So that you just add a annotation to a field of your model object aka scriptable object. And the binder will create the Property for it and updates the model object itself.
Currently I have something like that:
public class HoverData : ScriptableObject
{
public float height;
public float power;
}
And need an additional model object like:
public class HoverModel : IBindingContext
{
public IProperty<float> Height { get; }
public IProperty<float> Power { get; }
public HoverModel(HoverData hoverData){
Height = new Property<float>();
Power = new Property<float>();
Height.ValueChanged += (_, f) => hoverData.height = f;
Power.ValueChanged += (_, f) => hoverData.power = f;
}
}
would be cool when we could do something like:
public class HoverData : ScriptableObject
{
[Property]
public float height;
[Property]
public float power;
// And maybe also for commands?
[Command]
public void DoStuff() { }
}
Maybe I don't understand it correctly and you can show me a better way.
Suppose you have following files:
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
<ui:VisualElement style="flex-grow: 1; align-items: center; justify-content: center;">
<MyCustomControl binding-bool-path="ResultProperty, ResultToBooleanConverter" text="Label" style="font-size: 130px;" />
<MyCustomControl binding-bool-path="ResultProperty, InvertedResultToBooleanConverter" text="Label Inv" style="font-size: 130px;" />
</ui:VisualElement>
</ui:UXML>
public enum Result
{
Defeat,
Victory,
}
public class ResultToBooleanConverter : PropertyValueConverter<Result, Boolean>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override Boolean Convert(Result value)
{
return value == Result.Victory;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override Result ConvertBack(Boolean value)
{
return value ? Result.Victory : Result.Defeat;
}
}
public class InvertedResultToBooleanConverter : PropertyValueConverter<Result, Boolean>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override Boolean Convert(Result value)
{
return value != Result.Victory;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override Result ConvertBack(Boolean value)
{
return value ? Result.Defeat : Result.Victory;
}
}
public class MyView : DocumentView<MyViewModel>
{
protected override IValueConverter[] GetValueConverters()
{
return new IValueConverter[] { new ResultToBooleanConverter(), new InvertedResultToBooleanConverter() };
}
}
public class MyViewModel : IBindingContext
{
public IProperty<Result> ResultProperty { get; }
public TestCaseViewModel()
{
ResultProperty = new Property<Result>(Result.Victory);
}
}
[UxmlElement]
public partial class MyCustomControl : VisualElement, IBindableElement
{
[UxmlAttribute("binding-bool-path")]
private String _bindingBoolPath { get; set; }
private PropertyBindingData _isDisplayPathBindingData;
private IReadOnlyProperty<Boolean> _boolProperty;
public void SetBindingContext(IBindingContext context, IObjectProvider objectProvider)
{
if (String.IsNullOrWhiteSpace(_bindingBoolPath))
{
return;
}
_isDisplayPathBindingData ??= _bindingBoolPath.ToPropertyBindingData();
_boolProperty = objectProvider.RentProperty<Boolean>(context, _isDisplayPathBindingData);
_boolProperty.ValueChanged += OnBoolPropertyValueChanged;
UpdateControlValue(_boolProperty.Value);
}
public void ResetBindingContext(IObjectProvider objectProvider)
{
UpdateControlValue(false);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual void UpdateControlValue(Boolean newValue)
{
Debug.Log($"Value changed to: {newValue}");
}
private void OnBoolPropertyValueChanged(Object sender, Boolean flag)
{
UpdateControlValue(flag);
}
}
This configuration produces following exception
ArgumentException: An item with the same key has already been added. Key: -1662977815
System.Collections.Generic.Dictionary`2[TKey,TValue].TryInsert (TKey key, TValue value, System.Collections.Generic.InsertionBehavior behavior) (at <835c05e113da4a69bb097e93a682f230>:0)
System.Collections.Generic.Dictionary`2[TKey,TValue].Add (TKey key, TValue value) (at <835c05e113da4a69bb097e93a682f230>:0)
UnityMvvmToolkit.Core.Internal.ObjectHandlers.ValueConverterHandler.RegisterValueConverter (UnityMvvmToolkit.Core.Interfaces.IValueConverter valueConverter) (at D:/Projects/Bin/UnityMvvmToolkit/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/ObjectHandlers/ValueConverterHandler.cs:74)
UnityMvvmToolkit.Core.Internal.ObjectHandlers.ValueConverterHandler.RegisterValueConverters (UnityMvvmToolkit.Core.Interfaces.IValueConverter[] converters) (at D:/Projects/Bin/UnityMvvmToolkit/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/ObjectHandlers/ValueConverterHandler.cs:43)
UnityMvvmToolkit.Core.Internal.ObjectHandlers.ValueConverterHandler..ctor (UnityMvvmToolkit.Core.Interfaces.IValueConverter[] valueConverters) (at D:/Projects/Bin/UnityMvvmToolkit/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/Internal/ObjectHandlers/ValueConverterHandler.cs:18)
UnityMvvmToolkit.Core.BindingContextObjectProvider..ctor (UnityMvvmToolkit.Core.Interfaces.IValueConverter[] converters, System.Collections.Generic.IReadOnlyDictionary`2[TKey,TValue] collectionItemTemplates) (at D:/Projects/Bin/UnityMvvmToolkit/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Core/BindingContextObjectProvider.cs:26)
UnityMvvmToolkit.Common.MonoBehaviourView`1[TBindingContext].GetObjectProvider () (at D:/Projects/Bin/UnityMvvmToolkit/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Common/MonoBehaviourView.cs:75)
UnityMvvmToolkit.Common.MonoBehaviourView`1[TBindingContext].SetBindingContext () (at D:/Projects/Bin/UnityMvvmToolkit/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Common/MonoBehaviourView.cs:99)
UnityMvvmToolkit.Common.MonoBehaviourView`1[TBindingContext].Awake () (at D:/Projects/Bin/UnityMvvmToolkit/src/UnityMvvmToolkit.UnityPackage/Assets/Plugins/UnityMvvmToolkit/Runtime/Common/MonoBehaviourView.cs:28)
When the ItemSource
collection is cleared, the items in the ScrollView
are not removed.
Hi! I would like to have an ObservableAttribute support for private properties.
Currently it only supports private fields.
In cases you don't want to expose your private/protected properties, but still want to make them available just to the View having ObservableAttribute on such properties would be a good thing
New versions of Unity (2023.2+) provide built-in attributes for simple UI Toolkit elements declaration.
Unfortunately they are not compatible with Bindable elements provided with MVVM toolkit out of the box.
I suggest to add support for them with something like #if UNITY_2023_2_OR_NEWER
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.