示例#1
0
 private static ImmutableList <MyUser1> ReduceContacts(MyUser1 user, ImmutableList <MyUser1> contacts, object action)
 {
     contacts = contacts.MutateEntries(action, ReduceUser);
     if (action is ActionOnUser.AddContact a && a.IsTargetUser(user))
     {
         return(ImmutableExtensions.AddOrCreate <MyUser1>(contacts, (MyUser1)a.newContact));
     }
     return(contacts);
 }
示例#2
0
        /// <summary> This method tests the <see cref="SkipInUndoRedoLogic"/> interface that can be added to
        /// any Action (see eg the <see cref="ActionUpdateDownloadProgress"/> Action below). </summary>
        private void TestSkippableUndoActions(DataStore <MyAppState1> store)
        {
            using var t = Log.MethodEntered();

            var listenerWasCalledCounter = 0;

            store.AddStateChangeListener(x => x.downloadProgress, (int newDownloadPercentage) => {
                listenerWasCalledCounter++;
            }, triggerInstantToInit: false);

            store.Dispatch(new ActionLogoutUser());
            Assert.Null(store.GetState().user);

            var newUser = new MyUser1("Karl");

            store.Dispatch(new ActionLoginUser()
            {
                newLoggedInUser = newUser
            });
            Assert.Same(newUser, store.GetState().user);

            { // Now a download progress is simulated
                Assert.Equal(0, listenerWasCalledCounter);
                Assert.Equal(0, store.GetState().downloadProgress);

                store.Dispatch(new ActionUpdateDownloadProgress(10));
                Assert.Equal(10, store.GetState().downloadProgress);

                store.Dispatch(new ActionUpdateDownloadProgress(99));
                Assert.Equal(99, store.GetState().downloadProgress);

                store.Dispatch(new ActionUpdateDownloadProgress(100));
                Assert.Equal(100, store.GetState().downloadProgress);
                Assert.Equal(3, listenerWasCalledCounter);
            }

            // The download progress is not something that should be undoable, so
            // triggering an undo action now should directly skip all these
            // download progress events and undo the login Action directly:

            Assert.Same(newUser, store.GetState().user);
            store.Dispatch(new UndoAction <MyAppState1>());
            Assert.Null(store.GetState().user);
            Assert.Equal(0, store.GetState().downloadProgress);
            Assert.Equal(4, listenerWasCalledCounter);

            store.Dispatch(new RedoAction <MyAppState1>());
            Assert.Same(newUser, store.GetState().user);
            Assert.Equal(100, store.GetState().downloadProgress);
            Assert.Equal(5, listenerWasCalledCounter);
        }
示例#3
0
            private static MyUser1 ReduceUser(MyUser1 user, object action)
            {
                bool changed  = false;
                var  name     = user.name.Mutate(action, ReduceUserName, ref changed);
                var  age      = user.age.Mutate(action, ReduceUserAge, ref changed);
                var  contacts = user.contacts.Mutate(action, ReduceContacts, ref changed);
                var  myEnum   = user.myEnum.Mutate(action, ReduceMyEnum, ref changed);

                if (changed)
                {
                    // user.id can never change so always take it over from last state:
                    user = new MyUser1(user.id, name, age, contacts, myEnum);
                }
                return(user);
            }
示例#4
0
        private static class MyReducers1 { // The reducers to modify the immutable datamodel:
            // The most outer reducer is public to be passed into the store:
            public static MyUser1 ReduceUser(MyUser1 user, object action)
            {
                if (action is ActionLoginUser a1)
                {
                    return(a1.newLoggedInUser);
                }

                // Now the default pattern to call .Mutate on all fields and check via a changed flag if any
                // of the field were changed which would require a new instance of the object to be created:

                bool changed = false; // Will be set to true by the .Mutate method if any field changes:
                var  newName = user?.name.Mutate(action, ReduceUserName, ref changed);

                if (changed)
                {
                    return(new MyUser1(newName));
                }
                return(user); // None of the fields changed, old user can be returned
            }
示例#5
0
            private static MyUser1 ReduceUser(MyUser1 user, object action)
            {
                var changed = false;

                if (action is ActionChangeUserName a)
                {
                    if (a.targetUserId == user.id)
                    {
                        user.name = a.newName;
                        user.MarkMutated();
                        changed = true;
                    }
                }
                user.contacts = user.MutateField(user.contacts, action, ContactsReducer, ref changed);
                if (changed)
                {
                    user.MarkMutated();
                }
                return(user);
            }
示例#6
0
            private static List <MyContact1> ContactsReducer(MyUser1 parent, List <MyContact1> contacts, object action)
            {
                var changed = false;

                contacts.MutateEntries(action, ContactReducer, ref changed);
                if (action is ActionAddContact c)
                {
                    if (c.targetUserId == parent.id)
                    {
                        if (contacts == null)
                        {
                            contacts = new List <MyContact1>();
                        }
                        contacts.Add(c.newContact);
                        changed = true;
                    }
                }
                if (changed)
                {
                    parent.MarkMutated();
                }
                return(contacts);
            }
示例#7
0
 private static MyUser1 ReduceUser(MyUser1 user, object action)
 {
     if (action is ActionLogoutUser)
     {
         return(null);
     }
     if (action is ActionUserLoggedIn a1)
     {
         return(a1.newLoggedInUser);
     }
     if (action is ActionOnUser a)
     {
         bool userChanged    = false; // Will be set to true by the .Mutate method if any field changes:
         var  isTargetUser   = a.IsTargetUser(user);
         var  email          = user.email.Mutate(isTargetUser, a, ReduceUserEmail, ref userChanged);
         var  emailConfirmed = user.emailConfirmed.Mutate(isTargetUser, a, ReduceEmailConfirmed, ref userChanged);
         if (userChanged)
         {
             return(new MyUser1(email, emailConfirmed));
         }
     }
     return(user); // None of the fields changed, old user can be returned
 }
示例#8
0
 private static MyUser1 ReduceUser(MyUser1 user, object action)
 {
     if (action is ActionLogoutUser)
     {
         return(null);
     }
     if (action is ActionLoginUser a1)
     {
         return(a1.newLoggedInUser);
     }
     if (action is ActionOnUser a)
     {
         bool userChanged  = false;
         var  isTargetUser = a.IsTargetUser(user);
         var  name         = user.name.Mutate(isTargetUser, a, ReduceUserName, ref userChanged);
         var  age          = user.age.Mutate(isTargetUser, a, ReduceUserAge, ref userChanged);
         var  contacts     = user.MutateField(user.contacts, a, ReduceContacts, ref userChanged);
         if (userChanged)
         {
             return(new MyUser1(name, age, contacts));
         }
     }
     return(user); // None of the fields changed, old user can be returned
 }
示例#9
0
        public void ExampleUsage1()
        {
            var t = Log.MethodEntered("DataStoreExample3.ExampleUsage1");

            // A middleware that will allow to use mutable data in the data store:
            var mutableMiddleware = Middlewares.NewMutableDataSupport <MyAppState1>();

            // Add A logging middleware to log all dispatched actions:
            var loggingMiddleware = Middlewares.NewLoggingMiddleware <MyAppState1>();

            MyUser1 user = new MyUser1()
            {
                name = "Carl"
            };
            var model = new MyAppState1()
            {
                user = user
            };

            var store = new DataStore <MyAppState1>(MyReducers1.ReduceMyAppState1, model, loggingMiddleware, mutableMiddleware);

            IoC.inject.SetSingleton(store);
            store.storeName = "Store 4";

            // Setup 3 listeners that react when the name of the user or his contacts change:
            var userChangedCounter         = 0;
            var userNameChangedCounter     = 0;
            var contact1NameChangedCounter = 0;
            var contact2NameChangedCounter = 0;

            store.AddStateChangeListener(s => s.user, (MyUser1 theChangedUser) => {
                userChangedCounter++;
            });
            store.AddStateChangeListener(s => s.user?.name, (string theChangedName) => {
                userNameChangedCounter++;
            });
            store.AddStateChangeListener(s => s.user?.contacts?.FirstOrDefault()?.contactData.name, (_) => {
                contact1NameChangedCounter++;
            });
            store.AddStateChangeListener(s => s.user?.contacts?.Skip(1).FirstOrDefault()?.contactData.name, (_) => {
                contact2NameChangedCounter++;
            });

            var contact1 = new MyUser1()
            {
                name = "Tom"
            };
            { // Add a first contact to the user:
                Assert.Null(user.contacts);
                store.Dispatch(new ActionAddContact()
                {
                    targetUserId = user.id,
                    newContact   = new MyContact1()
                    {
                        contactData = contact1
                    }
                });
                Assert.Same(contact1, user.contacts.First().contactData);
                Assert.True(user.WasModifiedInLastDispatch());

                // Now that there is a contact 1 the listener was triggered:
                Assert.Equal(1, userChangedCounter);
                Assert.Equal(0, userNameChangedCounter);
                Assert.Equal(1, contact1NameChangedCounter);
                Assert.Equal(0, contact2NameChangedCounter);
            }

            var contact2 = new MyUser1()
            {
                name = "Bill"
            };
            { // Add a second contact to the user which should not affect contact 1:
                store.Dispatch(new ActionAddContact()
                {
                    targetUserId = user.id,
                    newContact   = new MyContact1()
                    {
                        contactData = contact2
                    }
                });
                Assert.Same(contact2, user.contacts.Last().contactData);
                Assert.True(user.WasModifiedInLastDispatch());
                Assert.False(contact1.WasModifiedInLastDispatch());

                Assert.Equal(2, userChangedCounter);
                Assert.Equal(0, userNameChangedCounter);
                Assert.Equal(1, contact1NameChangedCounter);
                Assert.Equal(1, contact2NameChangedCounter);
            }
            { // Change the name of contact 1 which should not affect contact 2:
                var newName1 = "Toooom";
                store.Dispatch(new ActionChangeUserName()
                {
                    targetUserId = contact1.id, newName = newName1
                });
                Assert.True(user.WasModifiedInLastDispatch());
                Assert.True(contact1.WasModifiedInLastDispatch());
                Assert.False(contact2.WasModifiedInLastDispatch());

                Assert.Equal(3, userChangedCounter);
                Assert.Equal(0, userNameChangedCounter);
                Assert.Equal(2, contact1NameChangedCounter);
                Assert.Equal(1, contact2NameChangedCounter);
            }
            { // Change the name of the user which should not affect the 2 contacts:
                var newName = "Caaaarl";
                Assert.NotEqual(newName, user.name);
                Assert.Equal(user.name, store.GetState().user.name);
                var tBeforeDispatch = user.LastMutation;
                store.Dispatch(new ActionChangeUserName()
                {
                    targetUserId = user.id, newName = newName
                });
                Assert.Equal(newName, store.GetState().user.name);
                Assert.Equal(newName, user.name);
                Assert.Same(model, store.GetState());

                Assert.NotEqual(tBeforeDispatch, user.LastMutation);
                Assert.True(user.WasModifiedInLastDispatch());
                Assert.False(contact1.WasModifiedInLastDispatch());
                Assert.False(contact2.WasModifiedInLastDispatch());

                Assert.Equal(4, userChangedCounter);
                Assert.Equal(1, userNameChangedCounter);
                Assert.Equal(2, contact1NameChangedCounter);
                Assert.Equal(1, contact2NameChangedCounter);
            }
            { // Marking an object mutated while not dispatching will throw an exception:
                Assert.Throws <InvalidOperationException>(() => {
                    user.name = "Cooorl";
                    user.MarkMutated();
                });
                Assert.Equal(4, userChangedCounter); // Count should not have changed
                Assert.Equal(1, userNameChangedCounter);
            }
        }
示例#10
0
 public bool IsTargetUser(MyUser1 user)
 {
     return(user.email == targetEmail);
 }
示例#11
0
 public MyAppState1(MyUser1 user = null)
 {
     this.user = user;
 }
示例#12
0
 public bool IsTargetUser(MyUser1 user)
 {
     return(user.name == targetUser);
 }
示例#13
0
 public MyAppState1(MyUser1 user, List <string> currentWeather, int downloadProgress)
 {
     this.user             = user;
     this.currentWeather   = currentWeather;
     this.downloadProgress = downloadProgress;
 }
示例#14
0
 public MyAppState1(MyUser1 user = null, List <string> currentWeather = null)
 {
     this.user           = user;
     this.currentWeather = currentWeather;
 }
示例#15
0
        public async Task RunTestTask()
        {
            MyUser1 initialState = null; // Initially no user is logged in
            var     store        = new DataStore <MyUser1>(MyReducers1.ReduceUser, initialState, Middlewares.NewLoggingMiddleware <MyUser1>());

            var links = gameObject.GetLinkMap();

            var loginButton           = links.Get <Button>("LoginBtn");
            var loginButtonWasClicked = loginButton.SetOnClickAction(async delegate {
                loginButton.gameObject.Destroy();
                await TaskV2.Run(async() => {
                    await TaskV2.Delay(1000);
                    store.Dispatch(new ActionLoginUser()
                    {
                        newLoggedInUser = new MyUser1("Karl")
                    });
                }).LogOnError();
                Assert.IsNotNull(store.GetState());
            });

            // Register a listener that is attached to the UI button to demonstrate that its no longer triggered once the button is destroyed:
            var userNameChangedCounter = 0;
            var subStateListener       = store.NewSubStateListenerForUnity(loginButton, user => user);

            subStateListener.AddStateChangeListener(x => x?.name, newName => {
                userNameChangedCounter++;
                Toast.Show("User name changed to " + newName);
                Assert.IsFalse(loginButton.IsDestroyed());
            }, triggerInstantToInit: false);

            var userInfoText1 = links.Get <InputField>("UserNameInput1");

            ConnectInputFieldUiToModel(userInfoText1, store);
            var userInfoText2 = links.Get <InputField>("UserNameInput2");

            ConnectInputFieldUiToModel(userInfoText2, store);

            var oldCounterValue = userNameChangedCounter;

            SimulateButtonClickOn("LoginBtn");
            await loginButtonWasClicked;

            Assert.IsTrue(loginButton.IsDestroyed());
            // Since the button was destroyed, the counter should not change anymore:
            Assert.AreEqual(oldCounterValue, userNameChangedCounter);

            Toast.Show("Changing user name from background thread...");
            await Task.Delay(2000);

            // When NewSubStateListener instead of NewSubStateListenerForUnity is used, the
            // event will arrive on the thread where it was dispatched:
            var wasCalledOnMainThread = true;

            store.NewSubStateListener(user => user).AddStateChangeListener(x => x.name, newName => {
                wasCalledOnMainThread = MainThread.isMainThread;
            }, triggerInstantToInit: false);

            await TaskV2.Run(async() => { store.Dispatch(new ChangeName()
                {
                    newName = "Caaarl"
                }); });

            Assert.AreEqual("Caaarl", store.GetState().name);
            Assert.IsFalse(wasCalledOnMainThread);
        }
示例#16
0
        public void ExampleUsage1()
        {
            var t = Log.MethodEntered("DataStoreExample1.ExampleUsage1");

            // Some initial state of the model (eg loaded from file when the app is started) is restored and put into the store:
            MyUser1 carl = new MyUser1(GuidV2.NewGuid(), "Carl", 99, null, MyUser1.MyEnum.State1);
            var     data = new MyAppState1(ImmutableDictionary <Guid, MyUser1> .Empty.Add(carl.id, carl), null);

            var store = new DataStore <MyAppState1>(MyReducers1.ReduceMyAppState1, data);

            var keepListenerAlive   = true;
            var usersChangedCounter = 0;

            store.AddStateChangeListener(state => state.users, (changedUsers) => {
                usersChangedCounter++;
                return(keepListenerAlive);
            }, triggerInstantToInit: false);

            ActionAddSomeId a1 = new ActionAddSomeId()
            {
                someId = GuidV2.NewGuid()
            };

            store.Dispatch(a1);
            Assert.Equal(0, usersChangedCounter); // no change happened in the users
            Assert.Equal(a1.someId, store.GetState().someUuids.Value.Single());

            MyUser1 carlsFriend = new MyUser1(GuidV2.NewGuid(), "Carls Friend", 50, null, MyUser1.MyEnum.State1);

            store.Dispatch(new ActionOnUser.AddContact()
            {
                targetUser = carl.id, newContact = carlsFriend
            });
            Assert.Equal(1, usersChangedCounter);
            Assert.Equal(carlsFriend, store.GetState().users[carlsFriend.id]);
            Assert.Contains(carlsFriend.id, store.GetState().users[carl.id].contacts);

            store.Dispatch(new ActionOnUser.ChangeName()
            {
                targetUser = carl.id, newName = "Karl"
            });
            Assert.Equal(2, usersChangedCounter);
            Assert.Equal("Karl", store.GetState().users[carl.id].name);


            store.Dispatch(new ActionOnUser.ChangeAge()
            {
                targetUser = carlsFriend.id, newAge = null
            });
            Assert.Equal(3, usersChangedCounter);
            Assert.Null(store.GetState().users[carlsFriend.id].age);

            Assert.NotEqual(MyUser1.MyEnum.State2, store.GetState().users[carl.id].myEnum);
            store.Dispatch(new ActionOnUser.ChangeEnumState()
            {
                targetUser = carl.id, newEnumValue = MyUser1.MyEnum.State2
            });
            Assert.Equal(MyUser1.MyEnum.State2, store.GetState().users[carl.id].myEnum);

            // Remove the listener from the store the next time an action is dispatched:
            keepListenerAlive = false;
            Assert.Equal(4, usersChangedCounter);
            store.Dispatch(new ActionOnUser.ChangeAge()
            {
                targetUser = carlsFriend.id, newAge = 22
            });
            Assert.Equal(5, usersChangedCounter);
            // Test that now the listener is removed and will not receive any updates anymore:
            store.Dispatch(new ActionOnUser.ChangeAge()
            {
                targetUser = carlsFriend.id, newAge = 33
            });
            Assert.Equal(5, usersChangedCounter);

            Log.MethodDone(t);
        }