浏览代码

move redux from sample to runtime.

add thunk middleware.
/main
kg 6 年前
当前提交
ee3832fe
共有 26 个文件被更改,包括 478 次插入79 次删除
  1. 4
      Runtime/foundation/basic_types.cs
  2. 2
      Runtime/foundation/node.cs
  3. 4
      Runtime/foundation/node.mixin.gen.cs
  4. 2
      Runtime/foundation/node.mixin.njk
  5. 4
      Runtime/widgets/navigator.cs
  6. 20
      Samples/ReduxSample/CounterApp/CounterAppSample.cs
  7. 4
      Samples/ReduxSample/ObjectFinder/FinderGameObject.cs
  8. 73
      Samples/ReduxSample/ObjectFinder/ObjectFinderApp.cs
  9. 93
      Samples/ReduxSample/ObjectFinder/Reducer.cs
  10. 6
      Samples/ReduxSample/ObjectFinder/StoreProvider.cs
  11. 2
      Runtime/redux/redux_thunk.cs.meta
  12. 8
      Runtime/redux.meta
  13. 19
      Runtime/redux/redux_logging.cs
  14. 24
      Runtime/redux/redux_thunk.cs
  15. 81
      Runtime/redux/store.cs
  16. 171
      Runtime/redux/widget_redux.cs
  17. 24
      Samples/ReduxSample/ObjectFinder/Middleware.cs
  18. 8
      Samples/ReduxSample/redux.meta
  19. 8
      Samples/ReduxSample/redux_logging.meta
  20. 0
      /Runtime/redux/redux_thunk.cs.meta
  21. 0
      /Runtime/redux/store.cs.meta
  22. 0
      /Runtime/redux/widget_redux.cs.meta
  23. 0
      /Runtime/redux/redux_logging.cs.meta

4
Runtime/foundation/basic_types.cs


return true;
}
if (ReferenceEquals(it, list)) {
return true;
}
if (it == null || list == null) {
return false;
}

2
Runtime/foundation/node.cs


foreach (var field in fields) {
if (!field.IsInitOnly) {
throw new UIWidgetsError(
type + " should be immutable. All public fields need to be readonly. " +
type + " is pure and should be immutable. All public fields need to be readonly. " +
field + " is not readonly.");
}
}

4
Runtime/foundation/node.mixin.gen.cs


static readonly Dictionary<_DependencyList, WeakReference> _canonicalObjects =
new Dictionary<_DependencyList, WeakReference>();
public bool alwaysUpdate { get; set; } = true; // if canonicalEquals should not be used.
public bool pure { get; set; } // pure = false, if canonicalEquals should not be used.
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) {

return false;
}
if (this.alwaysUpdate) {
if (!this.pure) {
return ReferenceEquals(this, obj);
} else {
return ReferenceEquals(this._getCanonical(), ((CanonicalMixinDiagnosticableTree) obj)._getCanonical());

2
Runtime/foundation/node.mixin.njk


return false;
}
if (this.alwaysUpdate) {
if (!this.pure) {
return ReferenceEquals(this, obj);
} else {
return ReferenceEquals(this._getCanonical(), ((CanonicalMixin{{with}}) obj)._getCanonical());

4
Runtime/widgets/navigator.cs


key: this._overlayKey,
initialEntries: this._initialOverlayEntries
)
) {
alwaysUpdate = true,
}
)
)
);
}

20
Samples/ReduxSample/CounterApp/CounterAppSample.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.engine;
using Unity.UIWidgets.Redux;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
using TextStyle = Unity.UIWidgets.painting.TextStyle;

public class CounterApp : StatelessWidget {
public override Widget build(BuildContext context) {
var store = new Store<CouterState>(reduce, new CouterState(),
ReduxLogging.Create<CouterState>());
ReduxLogging.create<CouterState>());
return new StoreProvider<CouterState>(store, this.createWidget());
}

child: new Column(
children: new List<Widget>() {
new StoreConnector<CouterState, string>(
converter: (state, dispatch) => $"Count:{state.count}",
builder: (context, countText) => new Text(countText, style: new TextStyle(
converter: (state) => $"Count:{state.count}",
builder: (context, countText, dispatcher) => new Text(countText, style: new TextStyle(
))
)),
pure: true
new StoreConnector<CouterState, Action>(
converter: (state, dispatch) => () => { dispatch(new CounterIncAction() {amount = 1}); },
builder: (context, onPress) => new CustomButton(
new StoreConnector<CouterState, object>(
converter: (state) => null,
builder: (context, _, dispatcher) => new CustomButton(
)), onPressed: () => { onPress(); })
)), onPressed: () => { dispatcher.dispatch(new CounterIncAction() {amount = 1}); }),
pure: true
),
}
)

4
Samples/ReduxSample/ObjectFinder/FinderGameObject.cs


// Update is called once per frame
void Update() {
var selectedId = StoreProvider.store.state.selected;
var selectedId = StoreProvider.store.getState().selected;
if (selectedId == this.GetInstanceID()) {
this.GetComponent<MeshRenderer>().material.color = new Color(1.0f, 0, 0, 1.0f);
}

}
void OnMouseDown() {
StoreProvider.store.Dispatch(new SelectObjectAction() {id = this.GetInstanceID()});
StoreProvider.store.dispatcher.dispatch(new SelectObjectAction() {id = this.GetInstanceID()});
}
}
}

73
Samples/ReduxSample/ObjectFinder/ObjectFinderApp.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.engine;
using Unity.UIWidgets.Redux;
using UnityEngine;
using Color = Unity.UIWidgets.ui.Color;
using TextStyle = Unity.UIWidgets.painting.TextStyle;
namespace Unity.UIWidgets.Sample.Redux.ObjectFinder {

Widget createRootWidget() {
return new StoreConnector<FinderAppState, ObjectFinderAppWidgetModel>(
(context, viewModel) => new ObjectFinderAppWidget(
viewModel, this.gameObject.name
pure: true,
builder: (context, viewModel, dispatcher) => new ObjectFinderAppWidget(
model: viewModel,
doSearch: (text) => dispatcher.dispatch(SearchAction.create(text)),
onSelect: (id) => dispatcher.dispatch(new SelectObjectAction() {id = id}),
title: this.gameObject.name
(state, dispacher) => new ObjectFinderAppWidgetModel() {
converter: (state) => new ObjectFinderAppWidgetModel() {
doSearch = (text) => dispacher(new SearchAction() {keyword = text}),
onSelect = (id) => dispacher(new SelectObjectAction() {id = id})
}
);
}

public delegate void onFindCallback(string keyword);
public class ObjectFinderAppWidgetModel {
public class ObjectFinderAppWidgetModel : IEquatable<ObjectFinderAppWidgetModel> {
public Action<string> doSearch;
public Action<int> onSelect;
public bool Equals(ObjectFinderAppWidgetModel other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return this.selected == other.selected && this.objects.equalsList(other.objects);
}
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return this.Equals((ObjectFinderAppWidgetModel) obj);
}
public override int GetHashCode() {
unchecked {
return (this.selected * 397) ^ this.objects.hashList();
}
}
public static bool operator ==(ObjectFinderAppWidgetModel left, ObjectFinderAppWidgetModel right) {
return Equals(left, right);
}
public static bool operator !=(ObjectFinderAppWidgetModel left, ObjectFinderAppWidgetModel right) {
return !Equals(left, right);
}
}
public class ObjectFinderAppWidget : StatefulWidget {

public readonly string title;
public ObjectFinderAppWidget(ObjectFinderAppWidgetModel model, string title, Key key = null) : base(key) {
public ObjectFinderAppWidget(
ObjectFinderAppWidgetModel model = null,
Action<string> doSearch = null,
Action<int> onSelect = null,
string title = null,
Key key = null) : base(key) {
this.doSearch = model.doSearch;
this.onSelect = model.onSelect;
this.doSearch = doSearch;
this.onSelect = onSelect;
this.title = title;
}

this._controller = new TextEditingController("");
this._focusNode = new FocusNode();
if (this.widget.doSearch != null) {
//scheduler.SchedulerBinding.instance.scheduleFrameCallback
Window.instance.scheduleMicrotask(() => this.widget.doSearch(""));
}

base.dispose();
}
public override Widget build(BuildContext context) {
public override Widget build(BuildContext context) {
Debug.Log("build ObjectFinderAppWidget");
return new Container(
padding: EdgeInsets.all(10),
decoration: new BoxDecoration(color: new Color(0x4FFFFFFF),

93
Samples/ReduxSample/ObjectFinder/Reducer.cs


using System;
using System.Collections.Generic;
using System.Linq;
using Unity.UIWidgets.Redux;
public class GameObjectInfo {
public class GameObjectInfo : IEquatable<GameObjectInfo> {
public bool Equals(GameObjectInfo other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return this.id == other.id && string.Equals(this.name, other.name);
}
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return this.Equals((GameObjectInfo) obj);
}
public override int GetHashCode() {
unchecked {
return (this.id * 397) ^ (this.name != null ? this.name.GetHashCode() : 0);
}
}
public static bool operator ==(GameObjectInfo left, GameObjectInfo right) {
return Equals(left, right);
}
public static bool operator !=(GameObjectInfo left, GameObjectInfo right) {
return !Equals(left, right);
}
public class FinderAppState {
public class FinderAppState : IEquatable<FinderAppState> {
public int selected;
public List<GameObjectInfo> objects;

}
public bool Equals(FinderAppState other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return this.selected == other.selected && Equals(this.objects, other.objects);
}
public override bool Equals(object obj) {
if (ReferenceEquals(null, obj)) {
return false;
}
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return this.Equals((FinderAppState) obj);
}
public override int GetHashCode() {
unchecked {
return (this.selected * 397) ^ (this.objects != null ? this.objects.GetHashCode() : 0);
}
}
public static bool operator ==(FinderAppState left, FinderAppState right) {
return Equals(left, right);
}
public static bool operator !=(FinderAppState left, FinderAppState right) {
return !Equals(left, right);
}
public class SearchAction {
public string keyword;
public static class SearchAction {
public static ThunkAction<FinderAppState> create(string keyword) {
return new ThunkAction<FinderAppState>((dispatcher, getState) => {
var objects = UnityEngine.Object.FindObjectsOfType(typeof(FinderGameObject)).Where(
obj => keyword == "" || obj.name.ToUpper().Contains(keyword.ToUpper())).Select(
obj => new GameObjectInfo {id = obj.GetInstanceID(), name = obj.name}).ToList();
dispatcher.dispatch(new SearchResultAction {keyword = keyword, results = objects});
return null;
});
}
}
[Serializable]

6
Samples/ReduxSample/ObjectFinder/StoreProvider.cs


using Unity.UIWidgets.Redux;
namespace Unity.UIWidgets.Sample.Redux.ObjectFinder {
public static class StoreProvider {
static Store<FinderAppState> _store;

}
var middlewares = new Middleware<FinderAppState>[] {
ReduxLogging.Create<FinderAppState>(),
GameFinderMiddleware.Create(),
ReduxLogging.create<FinderAppState>(),
ReduxThunk.create<FinderAppState>(),
};
_store = new Store<FinderAppState>(ObjectFinderReducer.Reduce,
new FinderAppState(),

2
Runtime/redux/redux_thunk.cs.meta


fileFormatVersion: 2
guid: d80e14622d3da4c6eaa3f72c29fa1fd3
guid: f837de040a3fa4a3fb3e6c344153e1eb
MonoImporter:
externalObjects: {}
serializedVersion: 2

8
Runtime/redux.meta


fileFormatVersion: 2
guid: 3a7bb5e4fb043482e8bf223c2d3fa76c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

19
Runtime/redux/redux_logging.cs


using UnityEngine;
namespace Unity.UIWidgets.Redux {
public static class ReduxLogging {
public static Middleware<State> create<State>() {
return (store) => (next) => new DispatcherImpl((action) => {
var previousState = store.getState();
var previousStateDump = JsonUtility.ToJson(previousState);
var result = next.dispatch(action);
var afterState = store.getState();
var afterStateDump = JsonUtility.ToJson(afterState);
Debug.LogFormat("Action name={0} data={1}", action.GetType().Name, JsonUtility.ToJson(action));
Debug.LogFormat("previousState=\n{0}", previousStateDump);
Debug.LogFormat("afterState=\n{0}", afterStateDump);
return result;
});
}
}
}

24
Runtime/redux/redux_thunk.cs


using System;
namespace Unity.UIWidgets.Redux {
public static class ReduxThunk {
public static Middleware<State> create<State>() {
return (store) => (next) => new DispatcherImpl((action) => {
var thunkAction = action as ThunkAction<State>;
if (thunkAction != null) {
return thunkAction.action(store.dispatcher, store.getState);
}
return next.dispatch(action);
});
}
}
public sealed class ThunkAction<State> {
public readonly Func<Dispatcher, Func<State>, object> action;
public ThunkAction(Func<Dispatcher, Func<State>, object> action) {
this.action = action;
}
}
}

81
Runtime/redux/store.cs


using System;
using System.Linq;
namespace Unity.UIWidgets {
public interface Dispatcher {
T dispatch<T>(object action);
object dispatch(object action);
}
public class DispatcherImpl : Dispatcher {
readonly Func<object, object> _impl;
public DispatcherImpl(Func<object, object> impl) {
this._impl = impl;
}
public T dispatch<T>(object action) {
if (this._impl == null) {
return default;
}
return (T) this._impl(action);
}
public object dispatch(object action) {
if (this._impl == null) {
return default;
}
return this._impl(action);
}
}
public delegate State Reducer<State>(State previousState, object action);
public delegate Func<Dispatcher, Dispatcher> Middleware<State>(Store<State> store);
public delegate void StateChangedHandler<State>(State action);
public class Store<State> {
public StateChangedHandler<State> stateChanged;
readonly Dispatcher _dispatcher;
readonly Reducer<State> _reducer;
State _state;
public Store(
Reducer<State> reducer,
State initialState = default,
params Middleware<State>[] middleware) {
this._reducer = reducer;
this._dispatcher = this._applyMiddleware(middleware);
this._state = initialState;
}
public Dispatcher dispatcher {
get { return this._dispatcher; }
}
public State getState() {
return this._state;
}
Dispatcher _applyMiddleware(params Middleware<State>[] middleware) {
return middleware.Reverse().Aggregate<Middleware<State>, Dispatcher>(
new DispatcherImpl(this._innerDispatch),
(current, middlewareItem) => middlewareItem(this)(current));
}
object _innerDispatch(object action) {
this._state = this._reducer(this._state, action);
if (this.stateChanged != null) {
this.stateChanged(this._state);
}
return action;
}
}
}

171
Runtime/redux/widget_redux.cs


using System;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;
using Unity.UIWidgets.widgets;
namespace Unity.UIWidgets.Redux {
public class StoreProvider<State> : InheritedWidget {
readonly Store<State> _store;
public StoreProvider(
Store<State> store = null,
Widget child = null,
Key key = null) : base(key: key, child: child) {
D.assert(store != null);
D.assert(child != null);
this._store = store;
}
public static Store<State> of(BuildContext context) {
var type = _typeOf<StoreProvider<State>>();
StoreProvider<State> provider = context.inheritFromWidgetOfExactType(type) as StoreProvider<State>;
if (provider == null) {
throw new UIWidgetsError("StoreProvider is missing");
}
return provider._store;
}
static Type _typeOf<T>() {
return typeof(T);
}
public override bool updateShouldNotify(InheritedWidget old) {
return !Equals(this._store, ((StoreProvider<State>) old)._store);
}
}
public delegate Widget ViewModelBuilder<ViewModel>(BuildContext context, ViewModel viewModel, Dispatcher dispatcher);
public delegate ViewModel StoreConverter<State, ViewModel>(State state);
public delegate bool ShouldRebuildCallback<ViewModel>(ViewModel previous, ViewModel current);
public class StoreConnector<State, ViewModel> : StatelessWidget {
public readonly ViewModelBuilder<ViewModel> builder;
public readonly StoreConverter<State, ViewModel> converter;
public readonly ShouldRebuildCallback<ViewModel> shouldRebuild;
public readonly bool pure;
public StoreConnector(
ViewModelBuilder<ViewModel> builder = null,
StoreConverter<State, ViewModel> converter = null,
bool pure = false,
ShouldRebuildCallback<ViewModel> shouldRebuild = null,
Key key = null) : base(key) {
D.assert(builder != null);
D.assert(converter != null);
this.pure = pure;
this.builder = builder;
this.converter = converter;
this.shouldRebuild = shouldRebuild;
}
public override Widget build(BuildContext context) {
return new _StoreListener<State, ViewModel>(
store: StoreProvider<State>.of(context),
builder: this.builder,
converter: this.converter,
pure: this.pure,
shouldRebuild: this.shouldRebuild
);
}
}
public class _StoreListener<State, ViewModel> : StatefulWidget {
public readonly ViewModelBuilder<ViewModel> builder;
public readonly StoreConverter<State, ViewModel> converter;
public readonly Store<State> store;
public readonly ShouldRebuildCallback<ViewModel> shouldRebuild;
public readonly bool pure;
public _StoreListener(
ViewModelBuilder<ViewModel> builder = null,
StoreConverter<State, ViewModel> converter = null,
Store<State> store = null,
bool pure = false,
ShouldRebuildCallback<ViewModel> shouldRebuild = null,
Key key = null) : base(key) {
D.assert(builder != null);
D.assert(converter != null);
D.assert(store != null);
this.store = store;
this.builder = builder;
this.converter = converter;
this.pure = pure;
this.shouldRebuild = shouldRebuild;
}
public override widgets.State createState() {
return new _StoreListenerState<State, ViewModel>();
}
}
class _StoreListenerState<State, ViewModel> : State<_StoreListener<State, ViewModel>> {
ViewModel latestValue;
public override void initState() {
base.initState();
this._init();
}
public override void dispose() {
this.widget.store.stateChanged -= this._handleStateChanged;
base.dispose();
}
public override void didUpdateWidget(StatefulWidget oldWidget) {
var oldStore = ((_StoreListener<State, ViewModel>) oldWidget).store;
if (this.widget.store != oldStore) {
oldStore.stateChanged -= this._handleStateChanged;
this._init();
}
base.didUpdateWidget(oldWidget);
}
void _init() {
this.widget.store.stateChanged += this._handleStateChanged;
this.latestValue = this.widget.converter(this.widget.store.getState());
}
void _handleStateChanged(State state) {
if (Window.hasInstance) {
this._innerStateChanged(state);
}
else {
using (WindowProvider.of(this.context).getScope()) {
this._innerStateChanged(state);
}
}
}
void _innerStateChanged(State state) {
var preValue = this.latestValue;
this.latestValue = this.widget.converter(this.widget.store.getState());
if (this.widget.shouldRebuild != null) {
if (!this.widget.shouldRebuild(preValue, this.latestValue)) {
return;
}
}
else if (this.widget.pure) {
if (Equals(preValue, this.latestValue)) {
return;
}
}
this.setState();
}
public override Widget build(BuildContext context) {
return this.widget.builder(context, this.latestValue, this.widget.store.dispatcher);
}
}
}

24
Samples/ReduxSample/ObjectFinder/Middleware.cs


using System.Linq;
using UnityEngine;
namespace Unity.UIWidgets.Sample.Redux.ObjectFinder {
public class GameFinderMiddleware {
public static Middleware<FinderAppState> Create() {
return (store) => (next) => (action) => {
if (action is SearchAction) {
var searchAction = (SearchAction) action;
var objects = Object.FindObjectsOfType(typeof(FinderGameObject)).Where((obj) => {
return searchAction.keyword == "" ||
obj.name.ToUpper().Contains(searchAction.keyword.ToUpper());
}).Select(obj => new GameObjectInfo {id = obj.GetInstanceID(), name = obj.name}).ToList();
var result = next(action);
store.Dispatch(new SearchResultAction() {keyword = searchAction.keyword, results = objects});
return result;
}
return next(action);
};
}
}
}

8
Samples/ReduxSample/redux.meta


fileFormatVersion: 2
guid: b6da4bbe4d8914b81a78b7d08ad4c7a4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Samples/ReduxSample/redux_logging.meta


fileFormatVersion: 2
guid: 5efe762e4ce804533b403e22eedb2e3b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

/Samples/ReduxSample/ObjectFinder/Middleware.cs.meta → /Runtime/redux/redux_thunk.cs.meta

/Samples/ReduxSample/redux/store.cs.meta → /Runtime/redux/store.cs.meta

/Samples/ReduxSample/redux/widget_redux.cs.meta → /Runtime/redux/widget_redux.cs.meta

/Samples/ReduxSample/redux_logging/redux_logging.cs.meta → /Runtime/redux/redux_logging.cs.meta

正在加载...
取消
保存