Compare commits

..
17 Commits
Author SHA1 Message Date
Eric Harding f437d50041 Merged in leak2 (pull request #84)
Leak2

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: John Colvin <john.colvin@bluetradesys.com>
2018-10-19 14:58:28 +00:00
Eric Harding c7bbcc1738 Update release notes 2018-10-19 10:33:30 -04:00
Eric Harding 3bdde95b42 Update release notes 2018-10-19 09:35:36 -04:00
Eric Harding 52e6be5d60 Merge branch 'prod' of bitbucket.org:bluetradesys/spark into leak2 2018-10-19 09:24:25 -04:00
Eric Harding bda76d0234 Reduce logging allocations for windows client 2018-10-19 09:11:59 -04:00
Eric Harding 7cb08ba90a Improve cleanup by releaseing references from Finalizeable handles. 2018-10-18 16:50:04 -04:00
Eric Harding ee6d05c737 Fix 2 potential NullReference in DIYGrid when DataContext goes to null 2018-10-18 16:49:29 -04:00
Eric Harding 62a5ee94e8 Add EnumerateGroup to SortedTree 2018-10-18 11:08:19 -04:00
Eric Harding f6e12d1664 Move SortedTreeTests to bts_tests 2018-10-18 10:43:10 -04:00
Eric Harding d022080b80 Leak: Fix WPF binding leaks in OrderBook 2018-10-17 17:44:47 -04:00
Eric Harding c2a5c278e9 Increase parallelism in workspace loading. 2018-10-17 13:08:33 -04:00
John Colvin f895e69aa9 get rid of misleading constructor in viewmodelbase (signature has default-initialization for some unused arguments) 2018-10-17 11:01:08 -05:00
Eric Harding 7c050a9e10 Add log warning on order book reset 2018-10-17 10:28:08 -04:00
John Colvin 38087d21d8 Leak(orderbook) - comment out PredicateAnimator in OrderBookRowVM 2018-10-16 22:41:28 -05:00
John Colvin 4b2e49c4aa Leak(orderbook) - make cell-stores lazy 2018-10-16 22:23:36 -05:00
John Colvin e31c0a8112 Leak (orderbook) - disable cell animations 2018-10-16 21:18:11 -05:00
John Colvin 876d6e0981 OrderBookVM - change orders subscription from LegacyTableUpdate to TableUpdate 2018-10-16 16:40:08 -05:00
20 changed files with 245 additions and 146 deletions

No files matched your search

+3
View File
@@ -1,3 +1,6 @@
1.3.4
- Changed default logging config for Windows. If Logging config is overridden for a system it should be adjusted to override only linux.
1.1.0
- With this release we dropped wild cards from limits, so any user
that has limits expressed in terms of wild cards will have to have
+6 -1
View File
@@ -1,8 +1,13 @@
1.3.4
- Significantly reduced client memory use
- Improved OrderBook performance
- Improved workspace load time
1.3.3
- Reduced server memory usage
1.3.2
- OrderBook performance improvements
- Improved OrderBook performance
- Fix some memory leaks when switching workspaces
1.3.1
@@ -47,7 +47,7 @@ namespace mbts.spark {
where T : struct =>
new OrderBookCellStore<T?>(row, column, prop_name, get_value, select_other, format, CompareUtils.nullable_comparison(compare), EqualityComparer.CreateNullable<T>(EqualityComparer<T>.Default.Equals));
class OrderBookCellStore<T> : IOrderBookCellStore<T> {
class OrderBookCellStore<T> : IOrderBookCellStore<T>, INotifyPropertyChanged {
readonly OrderBookRowVM _row;
readonly ICVOrderBookColumn _column;
readonly string _prop_name;
@@ -58,6 +58,10 @@ namespace mbts.spark {
readonly IEqualityComparer<T> _eq_comparer;
OrderBookCell<T> _current;
// Property changed handler to avoid WPF binding leak
// https://blog.jetbrains.com/dotnet/2014/09/04/fighting-common-wpf-memory-leaks-with-dotmemory/
public event PropertyChangedEventHandler PropertyChanged = (_,__) => { };
public OrderBookCellStore(
OrderBookRowVM row,
ICVOrderBookColumn column,
@@ -15,7 +15,7 @@ using OrderStatus = bts.api.orderman.OrderStatus;
namespace mbts.spark {
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM<OrderRecordTuple>, DIYGrid.IActionableRowVM {
public readonly PredicateAnimator<OrderBookRowVM, OrderRecordTuple> animator;
//public readonly PredicateAnimator<OrderBookRowVM, OrderRecordTuple> animator;
const string _unknown_security_price_format_string = "0.#########";
const int _unknown_security_price_num_fractional_digits = 9;
@@ -31,29 +31,65 @@ namespace mbts.spark {
OrderBookRowVM _parent;
OrderBookVM.BranchFilterSource _branch_filter_source;
// Core
IOrderBookCellStore<uint> _order_key_store;
IOrderBookCellStore<uint> _parent_order_key_store;
IOrderBookCellStore<uint> _security_key_store;
IOrderBookCellStore<SecurityImmutable?> _security_store;
IOrderBookCellStore<Venue> _exchange_store;
IOrderBookCellStore<string> _symbol_store;
IOrderBookCellStore<uint> _order_key_store_backing;
IOrderBookCellStore<uint> _parent_order_key_store_backing;
IOrderBookCellStore<uint> _security_key_store_backing;
IOrderBookCellStore<SecurityImmutable?> _security_store_backing;
IOrderBookCellStore<Venue> _exchange_store_backing;
IOrderBookCellStore<string> _symbol_store_backing;
// Def
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store;
IOrderBookCellStore<Side> _side_store;
IOrderBookCellStore<string> _user_store;
IOrderBookCellStore<string> _account_store;
IOrderBookCellStore<TimeInForce> _time_in_force_store;
IOrderBookCellStore<DateTime> _time_created_store;
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store_backing;
IOrderBookCellStore<Side> _side_store_backing;
IOrderBookCellStore<string> _user_store_backing;
IOrderBookCellStore<string> _account_store_backing;
IOrderBookCellStore<TimeInForce> _time_in_force_store_backing;
IOrderBookCellStore<DateTime> _time_created_store_backing;
// State
IOrderBookCellStore<DateTime> _last_change_store;
IOrderBookCellStore<OrderStatus> _status_store;
IOrderBookCellStore<double> _price_store;
IOrderBookCellStore<uint> _quantity_store;
IOrderBookCellStore<uint> _filled_quantity_store;
IOrderBookCellStore<double> _last_price_store;
IOrderBookCellStore<double> _avg_price_store;
IOrderBookCellStore<string> _last_msg_store;
IOrderBookCellStore<OrderSubType> _order_sub_type_store;
IOrderBookCellStore<DateTime> _last_change_store_backing;
IOrderBookCellStore<OrderStatus> _status_store_backing;
IOrderBookCellStore<double> _price_store_backing;
IOrderBookCellStore<uint> _quantity_store_backing;
IOrderBookCellStore<uint> _filled_quantity_store_backing;
IOrderBookCellStore<double> _last_price_store_backing;
IOrderBookCellStore<double> _avg_price_store_backing;
IOrderBookCellStore<string> _last_msg_store_backing;
IOrderBookCellStore<OrderSubType> _order_sub_type_store_backing;
// Core
IOrderBookCellStore<uint> _order_key_store => _order_key_store_backing ?? (_order_key_store_backing = CellFactory.create(this, _book_vm.columns.order_key_column, nameof(order_key), r => r._order.Key, r => r._order_key_store));
IOrderBookCellStore<uint> _parent_order_key_store => _parent_order_key_store_backing ?? (_parent_order_key_store_backing = CellFactory.create(this, _book_vm.columns.parent_key_column, nameof(parent_order_key), r => r._order.ParentKey, r => r._parent_order_key_store));
IOrderBookCellStore<uint> _security_key_store => _security_key_store_backing ?? (_security_key_store_backing = CellFactory.create(this, _book_vm.columns.security_key_column, nameof(security_key), r => r._order.security_key(), r => r._security_key_store));
IOrderBookCellStore<SecurityImmutable?> _security_store => _security_store_backing ?? (_security_store_backing = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.display_name()));
IOrderBookCellStore<Venue> _exchange_store => _exchange_store_backing ?? (_exchange_store_backing = CellFactory.create(this, _book_vm.columns.exchange_column, nameof(exchange), r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r._exchange_store));
IOrderBookCellStore<string> _symbol_store => _symbol_store_backing ?? (_symbol_store_backing = CellFactory.create(this, _book_vm.columns.symbol_column, nameof(symbol), r => r._symbol, r => r._symbol_store, v => !String.IsNullOrEmpty(v) ? v : "unknown symbol"));
// Def
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store => _order_type_store_backing ?? (_order_type_store_backing = CellFactory.create(this, _book_vm.columns.order_type_column, nameof(order_type), r => r._order.order_type(), r => r._order_type_store));
IOrderBookCellStore<Side> _side_store => _side_store_backing ?? (_side_store_backing = CellFactory.create(this, _book_vm.columns.side_column, nameof(side), r => r._order.side(), r => r._side_store));
IOrderBookCellStore<string> _user_store => _user_store_backing ?? (_user_store_backing = CellFactory.create(this, _book_vm.columns.user_column, nameof(user), r => _order.user(), r => r._user_store));
IOrderBookCellStore<string> _account_store => _account_store_backing ?? (_account_store_backing = CellFactory.create(this, _book_vm.columns.account_column, nameof(account), r => r._order.account(), r => r._account_store));
IOrderBookCellStore<TimeInForce> _time_in_force_store => _time_in_force_store_backing ?? (_time_in_force_store_backing = CellFactory.create(this, _book_vm.columns.time_in_force_column, nameof(time_in_force), r => r._order.time_in_force(), r => r._time_in_force_store));
IOrderBookCellStore<DateTime> _time_created_store => _time_created_store_backing ?? (_time_created_store_backing = CellFactory.create(this, _book_vm.columns.time_created_column, nameof(time_created), r => r._order.created(), r => r._time_created_store, format_date));
// State
IOrderBookCellStore<DateTime> _last_change_store => _last_change_store_backing ?? (_last_change_store_backing = CellFactory.create(this, _book_vm.columns.last_change_column, nameof(last_change), r => _order.last_change(), r => r._last_change_store, format_date));
IOrderBookCellStore<OrderStatus> _status_store => _status_store_backing ?? (_status_store_backing = CellFactory.create(this, _book_vm.columns.status_column, nameof(status), r => _order.status(), r => r._status_store));
IOrderBookCellStore<double> _price_store => _price_store_backing ?? (_price_store_backing = CellFactory.create(this, _book_vm.columns.price_column, nameof(price), r => _order.price(), r => r._price_store, format_price));
IOrderBookCellStore<uint> _quantity_store => _quantity_store_backing ?? (_quantity_store_backing = CellFactory.create(this, _book_vm.columns.qty_column, nameof(quantity), r => _order.quantity(), r => r._quantity_store));
IOrderBookCellStore<uint> _filled_quantity_store => _filled_quantity_store_backing ?? (_filled_quantity_store_backing = CellFactory.create(this, _book_vm.columns.filled_qty_column, nameof(filled_quantity), r => _order.filled(), r => r._filled_quantity_store));
IOrderBookCellStore<double> _last_price_store => _last_price_store_backing ?? (_last_price_store_backing = CellFactory.create(this, _book_vm.columns.last_price_column, nameof(last_price), r => _order.last_price(), r => r._last_price_store, format_price));
IOrderBookCellStore<double> _avg_price_store => _avg_price_store_backing ?? (_avg_price_store_backing = CellFactory.create(this, _book_vm.columns.avg_price_column, nameof(avg_price), r => _order.avg_price(), r => r._avg_price_store, format_price));
IOrderBookCellStore<string> _last_msg_store => _last_msg_store_backing ?? (_last_msg_store_backing = CellFactory.create(this, _book_vm.columns.last_message_column, nameof(last_msg), r => _order.last_msg(), r => r._last_msg_store));
IOrderBookCellStore<OrderSubType> _order_sub_type_store => _order_sub_type_store_backing ?? (_order_sub_type_store_backing = CellFactory.create(this, _book_vm.columns.sub_type_column, nameof(order_sub_type), r => r._order.sub_type(), r => r._order_sub_type_store, format_sub_type));
static string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
string format_price(double v) => _security.HasValue ?
_security.Value.ContractSpec.format(v) :
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
static string format_sub_type(OrderSubType v) {
switch (v) {
case OrderSubType.HedgingLeg: return "H";
case OrderSubType.QuotingLeg: return "Q";
default: return string.Empty;
}
}
public OrderBookRowVM(IBus bus, OrderBookVM source_vm, OrdersModel model, OrderRecordImmutable record, SecurityMD? s) {
_book_vm = source_vm;
@@ -61,43 +97,7 @@ namespace mbts.spark {
_order = record;
_security_md = s;
_symbol = _security_md.HasValue ? _security_md.Value.security.Symbol : null;
animator = new PredicateAnimator<OrderBookRowVM,OrderRecordTuple>(this);
// cells
cell_stores = new DIYGrid.IGridCellStore[] {
_security_store = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.display_name()),
_security_key_store = CellFactory.create(this, _book_vm.columns.security_key_column, nameof(security_key), r => r._order.security_key(), r => r._security_key_store),
_parent_order_key_store = CellFactory.create(this, _book_vm.columns.parent_key_column, nameof(parent_order_key), r => r._order.ParentKey, r => r._parent_order_key_store),
_order_key_store = CellFactory.create(this, _book_vm.columns.order_key_column, nameof(order_key), r => r._order.Key, r => r._order_key_store),
_symbol_store = CellFactory.create(this, _book_vm.columns.symbol_column, nameof(symbol), r => r._symbol, r => r._symbol_store, v => !String.IsNullOrEmpty(v) ? v : "unknown symbol"),
_time_created_store = CellFactory.create(this, _book_vm.columns.time_created_column, nameof(time_created), r => r._order.created(), r => r._time_created_store, format_date),
_user_store = CellFactory.create(this, _book_vm.columns.user_column, nameof(user), r => _order.user(), r => r._user_store),
_account_store = CellFactory.create(this, _book_vm.columns.account_column, nameof(account), r => r._order.account(), r => r._account_store),
_exchange_store = CellFactory.create(this, _book_vm.columns.exchange_column, nameof(exchange), r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r._exchange_store),
_time_in_force_store = CellFactory.create(this, _book_vm.columns.time_in_force_column, nameof(time_in_force), r => r._order.time_in_force(), r => r._time_in_force_store),
_order_type_store = CellFactory.create(this, _book_vm.columns.order_type_column, nameof(order_type), r => r._order.order_type(), r => r._order_type_store),
_order_sub_type_store = CellFactory.create(this, _book_vm.columns.sub_type_column, nameof(order_sub_type), r => r._order.sub_type(), r => r._order_sub_type_store, format_sub_type),
_side_store = CellFactory.create(this, _book_vm.columns.side_column, nameof(side), r => r._order.side(), r => r._side_store),
_last_change_store = CellFactory.create(this, _book_vm.columns.last_change_column, nameof(last_change), r => _order.last_change(), r => r._last_change_store, format_date),
_price_store = CellFactory.create(this, _book_vm.columns.price_column, nameof(price), r => _order.price(), r => r._price_store, format_price),
_quantity_store = CellFactory.create(this, _book_vm.columns.qty_column, nameof(quantity), r => _order.quantity(), r => r._quantity_store),
_filled_quantity_store = CellFactory.create(this, _book_vm.columns.filled_qty_column, nameof(filled_quantity), r => _order.filled(), r => r._filled_quantity_store),
_avg_price_store = CellFactory.create(this, _book_vm.columns.avg_price_column, nameof(avg_price), r => _order.avg_price(), r => r._avg_price_store, format_price),
_last_price_store = CellFactory.create(this, _book_vm.columns.last_price_column, nameof(last_price), r => _order.last_price(), r => r._last_price_store, format_price),
_status_store = CellFactory.create(this, _book_vm.columns.status_column, nameof(status), r => _order.status(), r => r._status_store),
_last_msg_store = CellFactory.create(this, _book_vm.columns.last_message_column, nameof(last_msg), r => _order.last_msg(), r => r._last_msg_store),
};
//
string format_price(double v) => _security.HasValue ? _security.Value.ContractSpec.format(v) :
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
string format_sub_type(OrderSubType v) {
switch (v) {
case OrderSubType.HedgingLeg: return "H";
case OrderSubType.QuotingLeg: return "Q";
default: return string.Empty;
}
}
//
//animator = new PredicateAnimator<OrderBookRowVM,OrderRecordTuple>(this);
_visible = backing(nameof(is_visible), true);
_expanded = backing(nameof(is_expanded), false);
_depth_level = backing(nameof(depth_level), () => {
@@ -212,7 +212,31 @@ namespace mbts.spark {
}
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores { get; }
DIYGrid.IGridCellStore[] _cell_stores_backing;
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => _cell_stores_backing ?? (_cell_stores_backing = new DIYGrid.IGridCellStore[] {
_security_store,
_security_key_store,
_parent_order_key_store,
_order_key_store,
_symbol_store,
_time_created_store,
_user_store,
_account_store,
_exchange_store,
_time_in_force_store,
_order_type_store,
_order_sub_type_store,
_side_store,
_last_change_store,
_price_store,
_quantity_store,
_filled_quantity_store,
_avg_price_store,
_last_price_store,
_status_store,
_last_msg_store
});
public static Stack<OrderBookRowVM> lineage(OrderBookRowVM node) {
var rv = new Stack<OrderBookRowVM>();
@@ -1,4 +1,5 @@
using bts.api;
using bts;
using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.shell;
@@ -69,12 +70,12 @@ namespace mbts.spark {
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(on_securities),
model.on_orders.OnDispatcher().AddRemoveOnly().Subscribe(on_orders),
model.on_orders.OnDispatcher().Subscribe(on_orders),
grid_settings_manager,
columns,
order_securities_subject);
void on_orders(LegacyTableUpdate<OrderRecordImmutable, uint> table) {
void on_orders(TableUpdate<OrderRecordImmutable, uint> table) {
// clear everything (most likely the server restarted)
if (table.removed.Count() == _order_rows.Count && !table.added.Any()) {
_selected_rows.Clear();
@@ -85,18 +86,14 @@ namespace mbts.spark {
_parent_to_children.clear();
_security_orders.clear();
missing_security_orders.clear();
Log.Ui.warn("OrderBook received reset message.");
return;
}
var changed_keys = new HashSet<uint>();
var added_securities = new List<SecurityImmutable>(1);
var removed_securities = new List<SecurityImmutable>(1);
foreach (var o in table.removed) {
// if state has it, then it wasn't actually removed, just changed
if (table.state.TryGetValue(o.Key, out OrderRecordImmutable changed_order)) {
changed_keys.Add(o.Key);
_order_rows_cache[o.Key].update_order(changed_order);
} else if (_order_rows_cache.TryPopValue(o.Key, out OrderBookRowVM row)) {
if (_order_rows_cache.TryPopValue(o.Key, out OrderBookRowVM row)) {
row.Dispose();
_order_rows.Remove(row);
if (_security_orders.removes_key(o.Def.SecurityKey, o.Key)) {
@@ -123,10 +120,10 @@ namespace mbts.spark {
missing_security_orders.remove(o.Key);
}
}
foreach (var ch in table.changes)
_order_rows_cache[ch.New.Key].update_order(ch.New);
var orphans = new Dictionary<uint, OrderBookRowVM>();
foreach (var o in table.added) {
if (changed_keys.Contains(o.Key))
continue;
SecurityMD? security_md;
if (security_dict.TryGetValue(o.Def.SecurityKey, out SecurityMD smd)) {
security_md = smd;
@@ -15,11 +15,9 @@ namespace mbts.spark {
CVRowTemplateKey = "CVRowTemplateKey";
public const double RowHeight = 17;
OrderBookVM _vm;
public OrderBookView(OrderBookVM vm) {
InitializeComponent();
_vm = vm;
DataContext = vm;
}
}
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using NUnit.Framework;
using mbts.collections.extensions;
using System.Collections;
using bts.collections;
namespace mbts.ui.utility
{
@@ -666,44 +666,6 @@ namespace mbts.collections.extensions {
}
}
public static class ListExtensions
{
#region RemoveWhere
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where) { foreach (var r in list.Where(where).ToList()) list.Remove(r); }
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where, Action<T> on_remove) {
int count = list.Count;
for (int i = 0; i < count; i++) {
var t = list[i];
if (where(t)) {
list.RemoveAt(i--);
--count;
on_remove(t);
}
}
}
private static Random rng = new Random();
public static void Shuffle<T>(this IList<T> list) {
int n = list.Count;
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
#endregion
public static void RemoveAt<T>(this IList<T> list, int index, int count) {
for (int i = 0; i < count; i++)
list.RemoveAt(index);
}
}
public static class DictionaryExtensions
{
#region GetValueOrDefault
@@ -29,6 +29,7 @@ namespace mbts.ui.settings
}
public Task<IShellWindow> restore(WindowState window) {
Log.Ui.info("Loading window '{0}'".fmt(window.title));
bool changed;
int count = 0;
do {
@@ -446,9 +446,6 @@ namespace mbts.ui.shell
public IEnumerable<string> AllErrors { get { return _errors.Values; } }
#endregion
protected Property<T> backing<T>(string name, T default_value, bool validate_now = false, params Func<T, bool>[] validation_steps) =>
backing(name, default_value, validation_steps.Select(v => new PropertyValidation<T>(v)).ToLArray(), validate_now);
protected Property<T> backing<T>(string name, T default_value, IEnumerable<PropertyValidation<T>> validation_steps, bool validate_now = false) {
void run_validation(T val) {
foreach (var pv in validation_steps) {
@@ -168,7 +168,7 @@ namespace mbts.ui.controls {
o.Resources[ActiveSelectionHighlightKey] = v;
o.Resources[InactiveSelectionHighlightKey] = new SolidColorBrush(v.Color) { Opacity = 0.2 }.frozen();
o.Resources[SelectionStrokeKey] = new Pen(o.SelectionHighlight.Color.to_brush().frozen(), 1).frozen();
foreach (var col in o.Columns.OfType<ISelectionHighlightListener>())
foreach (var col in o.Columns.empty_if_null().OfType<ISelectionHighlightListener>())
col.on_selection_fill(v);
}, (o, v) => v as SolidColorBrush ?? DefaultSelectionHighlight,
DefaultSelectionHighlight);
@@ -634,7 +634,7 @@ namespace mbts.ui.controls {
(o, v) => {
if (DoubleHelper.IsNaNOrInfinity(v))
v = 0;
if (!o._visible_columns.is_null_or_empty()) {
if (!o._visible_columns.is_null_or_empty() && o.Columns != null) {
double max_font_size = o.Columns.Max(c => c.font_size);
double min_row_height = o.RowPadding.Top + max_font_size + o.RowPadding.Bottom;
if (v < min_row_height)
@@ -31,7 +31,7 @@ namespace mbts.ui {
readonly ObservableCollection<TRow> _rows;
readonly ICollectionView _rows_view;
readonly ObservableCollection<ColumnBase> _columns;
readonly ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow> _animators;
//readonly ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow> _animators;
readonly Property<GridSettings> _grid_settings;
readonly DelegateProperty<FontFamily> _font_family;
readonly DelegateProperty<FontWeight> _font_weight;
@@ -59,18 +59,18 @@ namespace mbts.ui {
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
_animators = new ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow>(_rows = rows, create_animator);
//_animators = new ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow>(_rows = rows, create_animator);
_resources = new List<IDisposable> {
ObservableAutoWrapper.connect<SortDescriptionCollection, SortDescription, string>(
rows_view.SortDescriptions, live_sorting_props = ((ICollectionViewLiveShaping)rows_view).LiveSortingProperties, sd => sd.PropertyName),
(launch_settings_editor = new DialogCommand(create_settings_view))
};
//
PredicateAnimator<TRow, TRowData> create_animator(TRow r) {
var pa = new PredicateAnimator<TRow, TRowData>(r);
pa.set_predicate_animations(_animation_defs);
return pa;
}
//PredicateAnimator<TRow, TRowData> create_animator(TRow r) {
// var pa = new PredicateAnimator<TRow, TRowData>(r);
// pa.set_predicate_animations(_animation_defs);
// return pa;
//}
Window create_settings_view() {
var vm = new GridSettingsVM<TRowData, TPredicateBuilder>(owner_title, _bus, _orders_model, create_predicate_vm, create_column_predicate_vm);
// need to refresh before launching the settings-view bc users can chanve some grid-column-settings properties directly from the grid (e.g. width, is_collapsed, etc.)
@@ -134,9 +134,9 @@ namespace mbts.ui {
_animation_defs.add(pa.predicate_builder.create_predicate(_arg), PredicateAnimations.flat_definitions<TRow, TRowData>(pa.row_animations, pa.cell_animations, lookup_column));
}
}
foreach (var a in _animators) {
a.set_predicate_animations(_animation_defs);
}
//foreach (var a in _animators) {
// a.set_predicate_animations(_animation_defs);
//}
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
// don't add insult to injury by crashing **
if (grid_settings.columns != null && _columns != null) {
@@ -1,6 +1,7 @@
using bts.api;
using bts.spark;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
@@ -14,9 +15,10 @@ namespace mbts.ui.controls {
SetCurrentValue(FocusableProperty, false);
}
public SparkMenu(IEnumerable<SparkCommand> commands, IBus bus) : this() =>
ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
// ObservableCollection to avoid WPF Binding Leak
ItemsSource = new ObservableCollection<SparkCommandVM>(commands.Select(c => new SparkCommandVM(c, bus)));
public bool ShowText {
get { return (bool)GetValue(ShowTextProperty); }
@@ -6,6 +6,7 @@ using System.Windows.Interop;
using System.Runtime.InteropServices;
using mbts.ui.native;
using System.Windows;
using System.Windows.Threading;
namespace mbts.ui.shell
{
@@ -82,7 +83,14 @@ namespace mbts.ui.shell
return _hwnd;
}
protected override void DestroyWindowCore(HandleRef hwnd) { }
protected override void DestroyWindowCore(HandleRef hwnd) {
// Improve GC performance by keeping windows out of the Finalizer queue
if (hwnd.Wrapper is WPFPanel p) {
p.BeginInvoke((Action)(() => {
p.Dispose();
}));
}
}
[DllImport("user32.dll", SetLastError=true)]
static extern IntPtr SetParent(HandleRef hwndChild, HandleRef hwndParent);
@@ -222,16 +222,15 @@ namespace mbts.ui.shell
}
}
if (def.windows != null) {
foreach (var window in def.windows) {
Log.Ui.info("Loading window '{0}'".fmt(window.title));
var new_shell_window = await _window_restore.restore(window);
// user can close the launcher from the taskbar while a workspace is loading
if (_closing) {
return;
} else if (new_shell_window != null) {
await _wm.add(new_shell_window);
async Task restore(WindowState window) {
var w = await _window_restore.restore(window);
if (!_closing) {
await _wm.add(w);
}
}
var loading = def.windows.Select(restore);
await Task.WhenAll(loading);
if (_closing) return;
}
sw.Stop();
Log.Ui.info($"Time to load workspace '{name}': {sw.Elapsed}");
@@ -16,6 +16,7 @@ using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using mbts.ui;
using System.Collections.ObjectModel;
namespace bts.view {
public partial class SparkWindowView : UserControl {
@@ -36,7 +37,8 @@ namespace bts.view {
_menu.ShowText = false;
_grid.Children.Add(_menu);
if (ContextMenu != null) {
ContextMenu.ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
// ObservableCollection to avoid WPF Binding Leak
ContextMenu.ItemsSource = new ObservableCollection<SparkCommandVM>(commands.Select(c => new SparkCommandVM(c, bus)));
}
} else {
expand_button.Visibility = Visibility.Collapsed;
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace bts.collections {
public static class ListExtensions {
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where) { foreach (var r in list.Where(where).ToList()) list.Remove(r); }
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where, Action<T> on_remove) {
int count = list.Count;
for (int i = 0; i < count; i++) {
var t = list[i];
if (where(t)) {
list.RemoveAt(i--);
--count;
on_remove(t);
}
}
}
private static Random rng = new Random();
public static void Shuffle<T>(this IList<T> list) {
int n = list.Count;
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
public static void RemoveAt<T>(this IList<T> list, int index, int count) {
for (int i = 0; i < count; i++)
list.RemoveAt(index);
}
}
}
@@ -27,7 +27,7 @@ namespace bts.collections {
// Insert (return: true) or overwrite (return false) a new value
// NOTE: you should use AddAll if you are adding multiple values
bool Insert(T item) {
public bool Insert(T item) {
var item_id = id(item);
if (!_index.TryGetValue(item_id, out Node itemnode)) {
// Item is new
@@ -57,7 +57,7 @@ namespace bts.collections {
}
}
}
void Remove(T item) {
public void Remove(T item) {
var item_id = id(item);
if (_index.TryGetValue(item_id, out Node itemnode)) {
if (_index.TryGetValue(parent(item), out Node parentnode)) {
@@ -75,7 +75,7 @@ namespace bts.collections {
}
// Note: This method does not handle replacements correctly.
void AddAll(IEnumerable<T> items) {
public void AddAll(IEnumerable<T> items) {
// When adding multiple items we build the index first to avoid the children before parents problem
foreach (var item in items) {
//_index[id(item)] = new Node(item, _comparer);
@@ -103,6 +103,27 @@ namespace bts.collections {
}
}
}
Node parentmost(T item) {
if (_index.TryGetValue(parent(item), out Node pnode)) {
return parentmost(pnode.value);
}
else if (_index.TryGetValue(id(item), out Node mynode)) {
return mynode;
}
return null;
}
public IEnumerable<T> EnumerateGroup(T item) {
var pnode = parentmost(item);
if (pnode != null) {
yield return pnode.value;
foreach (var child in iterate(pnode.children)) {
yield return child;
}
}
}
public IEnumerator<T> GetEnumerator() {
return iterate(_tree).GetEnumerator();
}
@@ -1,6 +1,5 @@
using bts.collections;
using mbts.collections.extensions;
using NUnit.Framework;
using bts.test;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -9,7 +8,7 @@ using System.Text;
using System.Threading.Tasks;
namespace bts.tests {
[TestFixture]
[TestGroup]
class SortedTreeTests {
class Order {
@@ -92,5 +91,30 @@ namespace bts.tests {
}
}
[Test]
public void test_enumerate_group() {
var comparer = Comparer<Order>.Create((l, r) => l.name.CompareTo(r.name));
var input = orders.ToList();
input.Shuffle();
var tree = new SortedTree<Order, int>(comparer, id, pid, input);
Assert.SequenceEqual(tree, orders);
var children_of_a = tree.EnumerateGroup(orders.First(o => o.name == 'A'));
var also_children_of_a = tree.EnumerateGroup(orders.First(o => o.name == 'C'));
Assert.AreEqual(3, children_of_a.Count());
Assert.AreEqual(3, also_children_of_a.Count());
Assert.SequenceEqual(children_of_a, also_children_of_a);
var children_of_d = tree.EnumerateGroup(orders.First(o => o.name == 'F'));
var also_children_of_d = tree.EnumerateGroup(orders.First(o => o.name == 'E'));
Assert.AreEqual(children_of_d.First().name, 'D');
Assert.AreEqual(also_children_of_d.First().name, 'D');
Assert.SequenceEqual(children_of_d, also_children_of_d);
Assert.SequenceEqual(children_of_d.Select(o => o.name), new [] { 'D', 'E', 'F' });
var children_of_g = tree.EnumerateGroup(orders.First(o => o.name == 'G'));
Assert.SequenceEqual(children_of_g.Select(o => o.name), new [] { 'G' });
}
}
}
+11
View File
@@ -8,6 +8,17 @@ Logging {
}
}
Logging {
rule: "System.platform == 'windows'"
value {
console { default_level: "Error" }
syslog { default_level: "Info" }
file { default_level: "Info" }
initial_queue_size: 1024
max_queue_growth: 100
}
}
Logging
{
value