Esempio n. 1
0
        public async Task ExampleUsage2()
        {
            var t = Log.MethodEntered("DataStoreExample2.ExampleUsage2");

            // Add a thunk middleware to allow dispatching async actions:
            var thunkMiddleware = Middlewares.NewThunkMiddleware <MyAppState1>();

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

            // Add a recorder middleware to enable hot reload by replaying previously recorded actions:
            var recorder      = new ReplayRecorder <MyAppState1>();
            var recMiddleware = recorder.CreateMiddleware();

            var undoable = new UndoRedoReducer <MyAppState1>();
            // To allow undo redo on the full store wrap the main reducer with the undo reducer:
            var undoReducer = undoable.Wrap(MyReducers1.ReduceMyAppState1);

            var data  = new MyAppState1(null, null, 0); // the initial immutable state
            var store = new DataStore <MyAppState1>(undoReducer, data, loggingMiddleware, recMiddleware, thunkMiddleware);

            store.storeName = "Store 1";

            TestNormalDispatchOfActions(store);

            TestUndoAndRedo(store);

            await TestAsyncActions(store);

            await TestReplayRecorder(recorder, store);

            TestSkippableUndoActions(store);

            Log.MethodDone(t);
        }
Esempio n. 2
0
        private static DataStore <MyModel> NewDataStore()
        {
            MyModel model = new MyModel(null, ImmutableList <MyCircle> .Empty);
            Middleware <MyModel>      exampleMiddleware = Middlewares.NewLoggingMiddleware <MyModel>();
            UndoRedoReducer <MyModel> undoLogic         = new UndoRedoReducer <MyModel>();

            return(new DataStore <MyModel>(undoLogic.Wrap(MyReducer), model, exampleMiddleware));
        }
Esempio n. 3
0
        public static async Task ShowIn(ViewStack viewStack)
        {
            MyModel model = new MyModel(null, ImmutableList <MyUser> .Empty);
            Middleware <MyModel>      exampleMiddleware = Middlewares.NewLoggingMiddleware <MyModel>();
            UndoRedoReducer <MyModel> undoLogic         = new UndoRedoReducer <MyModel>();
            DataStore <MyModel>       store             = new DataStore <MyModel>(undoLogic.Wrap(MyReducer), model, exampleMiddleware);

            MyPresenter presenter = new MyPresenter();

            presenter.targetView = viewStack.ShowView("7GUIs_Task5_CRUD");
            await presenter.LoadModelIntoView(store);
        }
Esempio n. 4
0
        public override IEnumerator RunTest()
        {
            // Create an immutable datastore that will contain the data model in this example:
            var log = Middlewares.NewLoggingMiddleware <MyDataModel3>();
            IDataStore <MyDataModel3> store = new DataStore <MyDataModel3>(MainReducer, new MyDataModel3(), log);

            IoC.inject.SetSingleton(store);

            // Create a presenter that connectes the model with the view (the Unity UI):
            var currentUserPresenter = new MyUserUi3();

            // Set the target view by loading it from a prefab and setting the root GO:
            currentUserPresenter.targetView = ViewStackHelper.MainViewStack().ShowView("MyUserUi1");
            // Connect the model changes with the presenter:
            currentUserPresenter.ListenToStoreUpdates(store, state => state.currentUser);

            // Dispatch a first setUser action to update the UI:
            store.Dispatch(new ActionSetNewUser()
            {
                newUser = new MyUser3("Carl", 99)
            });
            // Delay needed since the UI update simulates a delay too:
            yield return(new WaitForSeconds(0.5f));

            // Check that the UI was automatically updated:
            AssertV2.AreEqual("Carl", currentUserPresenter.NameUi().text);
            AssertV2.AreEqual("99", currentUserPresenter.AgeUi().text);

            // Simulate that the user changed the model via the UI:
            store.Dispatch(new ActionUpdateUser()
            {
                target    = store.GetState().currentUser,
                newValues = new MyUser3("Paul", 0)
            });
            // Delay needed since the UI update simulates a delay too:
            yield return(new WaitForSeconds(2f));

            // Check that the UI was automatically updated:
            AssertV2.AreEqual("Paul", currentUserPresenter.NameUi().text);
            AssertV2.AreEqual("0", currentUserPresenter.AgeUi().text);
        }
Esempio n. 5
0
        private async Task TestReplayRecorderOnNewStore(ReplayRecorder <MyAppState1> recorder, MyAppState1 finalStateOfFirstStore)
        {
            var t = Log.MethodEntered("TestReplayRecorderOnNewStore");

            // Connect the recorder to the new store:
            var recMiddleware = recorder.CreateMiddleware();
            var undoable      = new UndoRedoReducer <MyAppState1>();
            var logging       = Middlewares.NewLoggingMiddleware <MyAppState1>();

            var data2  = new MyAppState1(null, null, 0);
            var store2 = new DataStore <MyAppState1>(undoable.Wrap(MyReducers1.ReduceMyAppState1), data2, logging, recMiddleware);

            store2.storeName = "Store 2";

            // Replaying the recorder will now fill the second store with the same actions:
            await recorder.ReplayStore();

            AssertEqualJson(finalStateOfFirstStore, store2.GetState());

            Log.MethodDone(t);
        }
Esempio n. 6
0
        public static async Task ShowIn(ViewStack viewStack)
        {
            // Call model unit tests manually before the UI is shown:
            CellsModelTests.TestFromAndToRowName();
            CellsModelTests.TestDataStoreTransitiveChanges();

            CellsModel model = new CellsModel(ImmutableDictionary <CellPos, Cell> .Empty);
            Middleware <CellsModel>      logging   = Middlewares.NewLoggingMiddleware <CellsModel>();
            UndoRedoReducer <CellsModel> undoLogic = new UndoRedoReducer <CellsModel>();
            DataStore <CellsModel>       store     = new DataStore <CellsModel>(undoLogic.Wrap(CellsReducers.MainReducer), model, logging);

            MyPresenter presenter = new MyPresenter();

            presenter.targetView = viewStack.ShowView("7GUIs_Task7_Cells");
            await presenter.LoadModelIntoView(store);

            await TaskV2.Delay(2000);

            Toast.Show("Now simulating some table model changes..");
            // Simulate changes in the model to check if the UI updates correctly:
            CellsModelTests.SimulateSomeChangesInModel(store);
        }
Esempio n. 7
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);
            }
        }
Esempio n. 8
0
        public async Task ExampleUsage1()
        {
            var t = Log.MethodEntered("DataStoreExample3.ExampleUsage1");

            // Add a thunk middleware to allow dispatching async actions:
            var thunkMiddleware = Middlewares.NewThunkMiddleware <MyAppState1>();

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

            var serverOutboxHandler = new ServerOutboxHandler <MyAppState1>();
            // To allow undo redo on the full store wrap the main reducer with the undo reducer:
            var outboxReducer = serverOutboxHandler.Wrap(MyReducers1.ReduceMyAppState1);
            var initialState  = new MyAppState1(); // the initial immutable state
            var store         = new DataStore <MyAppState1>(outboxReducer, initialState, loggingMiddleware, thunkMiddleware);

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

            { // Do a login which is an async server action that cant be cached optimistically and wont work offline:
                Func <Task> asyncLoginTask = async() => {
                    await TaskV2.Delay(100);

                    store.Dispatch(new ActionUserLoggedIn()
                    {
                        newLoggedInUser = new MyUser1("*****@*****.**")
                    });
                };
                await(store.Dispatch(asyncLoginTask) as Task);
            }
            { // Change the email a first time:
                var a = new ActionOnUser.ChangeEmail()
                {
                    targetEmail = "*****@*****.**", newEmail = "*****@*****.**"
                };
                store.Dispatch(a);
                Assert.Equal(a, store.GetState().serverOutbox.serverActions.First());
                Assert.False(store.GetState().user.emailConfirmed);
            }
            { // Change the email a second time:
                var a = new ActionOnUser.ChangeEmail()
                {
                    targetEmail = "*****@*****.**", newEmail = "*****@*****.**"
                };
                store.Dispatch(a);
                Assert.Equal(a, store.GetState().serverOutbox.serverActions.Last());
            }

            Assert.Equal(2, store.GetState().serverOutbox.serverActions.Count);
            await store.SyncWithServer(store.GetState().serverOutbox.serverActions.First());

            Assert.Single(store.GetState().serverOutbox.serverActions);
            await store.SyncWithServer(store.GetState().serverOutbox.serverActions.First());

            Assert.Empty(store.GetState().serverOutbox.serverActions);
            Assert.True(store.GetState().user.emailConfirmed);

            { // Simulate a server task that has a timeout:
                var a = new ActionOnUser.ChangeEmail()
                {
                    targetEmail = "*****@*****.**", newEmail = "*****@*****.**", simulateOneTimeout = true
                };
                store.Dispatch(a);
                Assert.Single(store.GetState().serverOutbox.serverActions);
                Assert.False(store.GetState().user.emailConfirmed);
                await store.SyncWithServer(a);

                Assert.Empty(store.GetState().serverOutbox.serverActions);
                Assert.Equal(2, a.sentToServerCounter);
                Assert.True(store.GetState().user.emailConfirmed);
            }
            { // Simulate the server rejecting an email change:
                var a = new ActionOnUser.ChangeEmail()
                {
                    targetEmail = "*****@*****.**", newEmail = "*****@*****.**", simulateError = true
                };
                store.Dispatch(a);
                await store.SyncWithServer(a);

                Assert.Empty(store.GetState().serverOutbox.serverActions);
                Assert.Equal("*****@*****.**", store.GetState().user.email);
                Assert.True(store.GetState().user.emailConfirmed);
            }
            { // Test persisting and restoring the full store and continue with the pending server requests:
                store.Dispatch(new ActionOnUser.ChangeEmail()
                {
                    targetEmail = "*****@*****.**", newEmail = "*****@*****.**"
                });
                store.Dispatch(new ActionOnUser.ChangeEmail()
                {
                    targetEmail = "*****@*****.**", newEmail = "*****@*****.**"
                });
                Assert.Equal(2, store.GetState().serverOutbox.serverActions.Count);
                Assert.False(store.GetState().user.emailConfirmed);
                Assert.Equal("*****@*****.**", store.GetState().user.email);

                // Simulate persisiting the store to disk and back into memory:
                string persistedStateJson = TypedJsonHelper.NewTypedJsonWriter().Write(store.GetState());

                store.Destroy(); // Destroy the old store before loading the state again into an new store
                var data2  = TypedJsonHelper.NewTypedJsonReader().Read <MyAppState1>(persistedStateJson);
                var store2 = new DataStore <MyAppState1>(outboxReducer, data2, loggingMiddleware, thunkMiddleware);
                IoC.inject.SetSingleton(store2, overrideExisting: true);
                store2.storeName = "Store 3 (2)";

                Assert.Equal(2, store2.GetState().serverOutbox.serverActions.Count);
                Assert.False(store2.GetState().user.emailConfirmed);
                Assert.Equal("*****@*****.**", store2.GetState().user.email);

                // Sync the pending server tasks one after another:
                foreach (var serverAction in store2.GetState().serverOutbox.serverActions)
                {
                    await store2.SyncWithServer(serverAction);
                }
                Assert.True(store2.GetState().user.emailConfirmed);
                Assert.NotNull(store2.GetState().serverOutbox);
                Assert.Empty(store2.GetState().serverOutbox.serverActions);
            }
        }
Esempio n. 9
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);
        }
Esempio n. 10
0
        public static void TestDataStoreTransitiveChanges()
        {
            CellsModel model = new CellsModel(ImmutableDictionary <CellPos, Cell> .Empty);
            var        store = new DataStore <CellsModel>(CellsReducers.MainReducer, model, Middlewares.NewLoggingMiddleware <CellsModel>());

            store.Dispatch(new MyActions.SetCell("A", 1, "1 + 1"));
            var  cells = store.SelectElement(s => s.cells);
            Cell a1    = cells()[new CellPos("A", 1)];

            Assert.Equal(2, a1.value);

            // B1 will depend on A1:
            store.Dispatch(new MyActions.SetCell("B", 1, "3 * A1"));
            Cell b1 = cells()[new CellPos("B", 1)];

            Assert.Equal(6, b1.value);

            // C1 will depend on A1 and B1:
            store.Dispatch(new MyActions.SetCell("C", 1, "B1 + 3 - A1"));
            Cell c1 = cells()[new CellPos("C", 1)];

            Assert.Equal(7, c1.value);

            // D1 will depend on C1 (so transitivly also on A1 and B1):
            store.Dispatch(new MyActions.SetCell("D", 1, "2 * C1"));
            Cell d1 = cells()[new CellPos("D", 1)];

            Assert.Equal(14, d1.value);

            // Now changing A1 must have affects to B1, C1, D1 as well:
            store.Dispatch(new MyActions.SetCell("A", 1, "4 + 1"));
            c1 = cells()[new CellPos("C", 1)];
            Assert.Equal(13, c1.value);
            d1 = cells()[new CellPos("D", 1)];
            Assert.Equal(26, d1.value);

            // Select cell C1:
            store.Dispatch(new MyActions.SelectCell(c1.pos));
            Assert.True(cells()[new CellPos("C", 1)].isSelected);
            // Select cell D1:
            store.Dispatch(new MyActions.SelectCell(d1.pos));
            Assert.True(cells()[new CellPos("D", 1)].isSelected);
            Assert.False(cells()[new CellPos("C", 1)].isSelected);

            store.Dispatch(new MyActions.SetCell("A", 3, "1"));
            store.Dispatch(new MyActions.SetCell("A", 2, "A3 + 1"));
            Assert.Throws <MyActions.SetCell.SelfRefException>(() => {
                store.Dispatch(new MyActions.SetCell("A", 3, "A2 + 1"));
            });
        }
Esempio n. 11
0
        public static async Task TestLoggingOverhead()
        {
            StopwatchV2 t1, t2;

            {
                t1 = Log.MethodEntered("SimulateManyChangesInModel without logging");
                CellsModel model = new CellsModel(ImmutableDictionary <CellPos, Cell> .Empty);
                var        store = new DataStore <CellsModel>(CellsReducers.MainReducer, model);
                await SimulateManyChangesInModel(store);

                Log.MethodDone(t1);
            }
            {
                t2 = Log.MethodEntered("SimulateManyChangesInModel without logging");
                CellsModel model = new CellsModel(ImmutableDictionary <CellPos, Cell> .Empty);
                var        store = new DataStore <CellsModel>(CellsReducers.MainReducer, model, Middlewares.NewLoggingMiddleware <CellsModel>());
                await SimulateManyChangesInModel(store);

                Log.MethodDone(t2);
            }
            // Logging makes mutating the model at least double as slow:
            Assert.True(t1.ElapsedMilliseconds * 2 < t2.ElapsedMilliseconds, $"t1={t1}, t2={t2}");
        }