public static float Length2Sq(this PointF p) { return(Math_.Sqr(p.X) + Math_.Sqr(p.Y)); }
protected override void OnMouseWheel(MouseWheelEventArgs args) { base.OnMouseWheel(args); // If there is a mouse op in progress, forward the event var op = MouseOperations.Active; if (op != null && !op.Cancelled) { op.MouseWheel(args); if (args.Handled) { return; } } var location = args.GetPosition(this); var along_ray = Options.MouseCentredZoom || Keyboard.Modifiers.HasFlag(ModifierKeys.Alt); var chart_pt = ClientToChart(location); var hit = HitTestZone(location, Keyboard.Modifiers, args.ToMouseBtns()); // Batch mouse wheel events into 100ms groups var defer_nav_checkpoint = DeferNavCheckpoints(); Dispatcher_.BeginInvokeDelayed(() => { Util.Dispose(ref defer_nav_checkpoint !); SaveNavCheckpoint(); }, TimeSpan.FromMilliseconds(100)); var scale = 0.001f; if (Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)) { scale *= 0.1f; } if (Keyboard.Modifiers.HasFlag(ModifierKeys.Alt)) { scale *= 0.01f; } var delta = Math_.Clamp(args.Delta * scale, -0.999f, 0.999f); var chg = (string?)null; // If zooming is allowed on both axes, translate the camera if (hit.Zone == EZone.Chart && XAxis.AllowZoom && YAxis.AllowZoom) { // Translate the camera along a ray through 'point' var loc = Gui_.MapPoint(this, Scene, location); Scene.Window.MouseNavigateZ(loc.ToPointF(), args.ToMouseBtns(Keyboard.Modifiers), args.Delta, along_ray); chg = nameof(SetRangeFromCamera); } // Otherwise, zoom on the allowed axis only else if (hit.Zone == EZone.XAxis || (hit.Zone == EZone.Chart && !YAxis.AllowZoom)) { if (hit.ModifierKeys.HasFlag(ModifierKeys.Control) && XAxis.AllowScroll) { // Scroll the XAxis XAxis.Shift(XAxis.Span * delta); chg = nameof(SetCameraFromRange); } else if (!hit.ModifierKeys.HasFlag(ModifierKeys.Control) && XAxis.AllowZoom) { // Change the aspect ratio by zooming on the XAxis var x = along_ray ? chart_pt.X : XAxis.Centre; var left = (XAxis.Min - x) * (1f - delta); var rite = (XAxis.Max - x) * (1f - delta); XAxis.Set(x + left, x + rite); if (Options.LockAspect != null) { YAxis.Span *= (1f - delta); } chg = nameof(SetCameraFromRange); } } else if (hit.Zone == EZone.YAxis || (hit.Zone == EZone.Chart && !XAxis.AllowZoom)) { if (hit.ModifierKeys.HasFlag(ModifierKeys.Control) && YAxis.AllowScroll) { // Scroll the YAxis YAxis.Shift(YAxis.Span * delta); chg = nameof(SetCameraFromRange); } else if (!hit.ModifierKeys.HasFlag(ModifierKeys.Control) && YAxis.AllowZoom) { // Change the aspect ratio by zooming on the YAxis var y = along_ray ? chart_pt.Y : YAxis.Centre; var left = (YAxis.Min - y) * (1f - delta); var rite = (YAxis.Max - y) * (1f - delta); YAxis.Set(y + left, y + rite); if (Options.LockAspect != null) { XAxis.Span *= (1f - delta); } chg = nameof(SetCameraFromRange); } } switch (chg) { // Update the axes from the camera position case nameof(SetRangeFromCamera): SetRangeFromCamera(); NotifyPropertyChanged(nameof(ValueAtPointer)); Invalidate(); break; // Set the camera position from the new axis ranges case nameof(SetCameraFromRange): SetCameraFromRange(); NotifyPropertyChanged(nameof(ValueAtPointer)); Invalidate(); break; } }
public Bond(int perm, Element elem1, Element elem2, GameConstants consts) { Perm = perm; // The electro-static force between two charged objects is F = k*Q*q/r² // Assume elem1 and elem2 are separated such that their outermost electron shells just touch // The total bond strength is the sum of the electro static forces: // P1 - P2 (repulsive), E1 - E2 (repulsive), P1 - E2 (attractive), P2 - E1 (attractive) // Assuming ionic/covalent bonding only, P1 and P2 can share electrons in their outer orbital. // The proton charges are the effective (Zeff) positive charge, the electron charge is // the charge of the maximum number of electrons that can be borrowed when trying to fill the // the outer orbital. // Find the number of electrons available to be shared between elem1 and elem2 in a covalent bond var shareable = Shareable(elem1, elem2); // Constant to make the strength reasonable numbers var scaler = 1e11 * consts.CoulombConstant * Math_.Sqr(consts.ElectronCharge) / Math_.Sqr(10e-12); // Assume these electrons sit between the two atoms, calculate the electro-static // force between the electrons and each element var strength = 0.0; Order = 0; // Order is the number of electrons actually shared in the bond for (int i = 0; i != shareable; ++i) { // This is the charge experienced by each element in relation to shared electron 'i' var charge1 = elem1.EffectiveCharge(consts, elem1.AtomicNumber + i + 1); var charge2 = elem2.EffectiveCharge(consts, elem2.AtomicNumber + i + 1); // If either element experiences a negative charge, then this shared election does // not contribute to the bond and no further shared electrons will either if (charge1 < 0 || charge2 < 0) { break; } // Accumulate the attractive forces strength += scaler * charge1 * 1.0 / Math_.Sqr(elem1.ValenceOrbitalRadius); strength += scaler * charge2 * 1.0 / Math_.Sqr(elem2.ValenceOrbitalRadius); ++Order; } { // Remove the effective charge repulsive force var charge1 = elem1.EffectiveCharge(consts, elem1.AtomicNumber + Order); var charge2 = elem2.EffectiveCharge(consts, elem2.AtomicNumber + Order); strength -= scaler * charge1 * charge2 / Math_.Sqr(elem1.ValenceOrbitalRadius + elem2.ValenceOrbitalRadius); } // Scale the strength by 'Order' // Relationship (got from Carbon) is Strength = BaseStrength * Order ^ 0.8 Count = new int[Math.Max(Order, 1)]; BaseStrength = strength * Math.Pow(Order, -0.8); // Ionicity > ~1.8 (Paulie scale) results in an ionic bond (as opposed to covalent bond). // The atoms of covalent materials are bound tightly to each other in stable molecules, but those // molecules are generally not very strongly attracted to other molecules in the Compound. On the // other hand, the atoms (ions) in ionic materials show strong attractions to other ions in their // vicinity. This generally leads to low melting points for covalent solids, and high melting points // for ionic solids. Ionicity = Math.Abs(elem2.Electronegativity - elem1.Electronegativity); }
/// <summary> /// Write the user input into the console and update the caret location. /// Set 'redraw' true if everything up to '_caret' is unchanged.</summary> private void Update(bool redraw) { // Notes: // - It's much easier to treat the console buffer as a 1D array. // - '_caret' is the position in the console buffer that corresponds to '_anchor'. // Callers can move the visible portion of the user input by adjusting '_caret' and '_anchor'. // - The console buffer may get resized. // - Writing to the console can cause it to roll over. // - There seems to be a weird Linux-only bug where pasting text that extends past // the end of the console buffer does not cause it to automatically roll over. // I haven't found a work-around for this, but it only seems to be a problem for paste, // and the full string pasted is added to the user input buffer. // // Think of it like this: // _caret // Console Buffer: [---------------V------------] (1D array) // User Input: (=====================^===I==) (1D array) // _anchor using (_console.SyncWrites()) { var buf_length = _console.BufferLength; var buf_size = _console.BufferSize; if (buf_size.IsEmpty) { return; } // Determine if the whole user input needs redrawing var moved = MoveToNextLine; var resized = buf_size != _last_size; redraw |= moved | resized; // Erase the previous buffer text. // If 'redraw', clear from the start of the user input. // Otherwise, just erase from the insert position onwards. _console.Clear( _console.NormaliseLocation(Pt(_caret - (redraw ? _anchor : 0))), _console.NormaliseLocation(Pt(_caret + (_last_length - _anchor))), false); // If the caret was moved by something else, set '_caret' to the next new line if (resized) { var pt = Pt(_caret, _last_size.Width); _caret = Idx(pt); } if (moved) { var pt0 = Pt(_caret); var pt1 = _console.Location; pt0.Y = Math_.Clamp(pt1.Y + (pt1.X != 0 ? 1 : 0), 0, buf_size.Height - 1); _caret = Idx(pt0); } // Write the user input to the console buffer. // Always write up to 'Insert', even if it causes a roll of the buffer. // For text after the insert position, only write up to the end of the buffer. int origin = 0, beg = 0, len = 0; if (redraw) { // Where in the console buffer to start writing to origin = Math_.Clamp(_caret - _anchor, 0, buf_length - 1); // Where in the user input to read from beg = Math_.Clamp(_anchor - _caret, 0, _line.Length); // The length of user input to write // Clamp to within the console buffer, unless the 'Insert' position extends beyond it. len = Math_.Clamp(_line.Length - beg, 0, buf_length - origin - 1); len = Math.Max(len, Insert - beg); } // Otherwise, just write forwards from '_anchor' else { // Where in the console buffer to start writing to origin = Math_.Clamp(_caret, 0, buf_length - 1); // Where in the user input to read from beg = Math_.Clamp(_anchor, 0, _line.Length); // The length of user input to write // Clamp to within the console buffer, unless the 'Insert' position extends beyond it. len = Math_.Clamp(_line.Length - beg, 0, buf_length - origin - 1); len = Math.Max(len, Insert - beg); } // Write to the console (this could invalidate 'origin') _console.Location = _console.NormaliseLocation(Pt(origin)); _console.Write(_line.ToString(beg, len)); // Move the caret position to match the 'Insert' position. _caret = Math_.Clamp(_caret + (Insert - _anchor), 0, buf_length - 1); _console.Location = _console.NormaliseLocation(Pt(_caret)); MoveToNextLine = false; // Save the values used in this update _last_length = _line.Length; _last_size = buf_size; _anchor = Insert; } }
/// <summary>Handle mouse messages over 'Target' and perform resizing</summary> public bool PreFilterMessage(ref Message m) { if (m.HWnd == Target.Handle || Win32.IsChild(Target.Handle, m.HWnd)) { switch (m.Msg) { case Win32.WM_LBUTTONDOWN: #region { var pt = Control.MousePosition; m_mask = Mask(pt); if (m_mask != EBoxZone.None) { Target.Cursor = m_mask.ToCursor(); m_grab = pt; m_size = Target.Size; m_loc = Target.Location; Target.Capture = true; return(true); } break; } #endregion case Win32.WM_MOUSEMOVE: #region { var pt = Control.MousePosition; if (m_grab != null) { var mn = Target.MinimumSize; var mx = Target.MaximumSize != Size.Empty ? Target.MaximumSize : new Size(int.MaxValue, int.MaxValue); var delta = Point_.Subtract(pt, m_grab.Value); if ((m_mask & EBoxZone.Right) != 0) { Target.Width = Math_.Clamp(m_size.Width + delta.Width, mn.Width, mx.Width); } if ((m_mask & EBoxZone.Bottom) != 0) { Target.Height = Math_.Clamp(m_size.Height + delta.Height, mn.Height, mx.Height); } if ((m_mask & EBoxZone.Left) != 0) { Target.Width = Math_.Clamp(m_size.Width - delta.Width, mn.Width, mx.Width); Target.Left = m_loc.X + m_size.Width - Target.Width; } if ((m_mask & EBoxZone.Top) != 0) { Target.Height = Math_.Clamp(m_size.Height - delta.Height, mn.Height, mx.Height); Target.Top = m_loc.Y + m_size.Height - Target.Height; } return(true); } else if (!Win32.IsChild(Target.Handle, m.HWnd)) { var mask = Mask(pt); if (mask != m_mask) { Target.Cursor = mask.ToCursor(); m_mask = mask; } } else { Target.Cursor = Cursors.Default; } break; } #endregion case Win32.WM_LBUTTONUP: #region { if (m_grab != null) { m_grab = null; Target.Capture = false; return(true); } break; } #endregion case Win32.WM_MOUSELEAVE: #region { m_mask = EBoxZone.None; break; } #endregion } } return(false); }
/// <summary>Show the view3D context menu</summary> public void ShowContextMenu() { if (Window == null) { return; } var context_menu = new ContextMenuStrip(); context_menu.Closed += (s, a) => Refresh(); { // View var view_menu = context_menu.Items.Add2(new ToolStripMenuItem("View") { Name = CMenuItems.View }); { // Show focus var opt = view_menu.DropDownItems.Add2(new ToolStripMenuItem("Show Focus") { Name = CMenuItems.ViewMenu.ShowFocus }); opt.Checked = Window.FocusPointVisible; opt.Click += (s, a) => { Window.FocusPointVisible = !Window.FocusPointVisible; Refresh(); }; } { // Show Origin var opt = view_menu.DropDownItems.Add2(new ToolStripMenuItem("Show Origin") { Name = CMenuItems.ViewMenu.ShowOrigin }); opt.Checked = Window.OriginPointVisible; opt.Click += (s, a) => { Window.OriginPointVisible = !Window.OriginPointVisible; Refresh(); }; } { // Show coords } { // Axis Views var opt = view_menu.DropDownItems.Add2(new ToolStripComboBox("Views") { Name = CMenuItems.ViewMenu.Views, DropDownStyle = ComboBoxStyle.DropDownList }); opt.Items.Add("Views"); opt.Items.Add("Axis +X"); opt.Items.Add("Axis -X"); opt.Items.Add("Axis +Y"); opt.Items.Add("Axis -Y"); opt.Items.Add("Axis +Z"); opt.Items.Add("Axis -Z"); opt.Items.Add("Axis -X,-Y,-Z"); opt.SelectedIndex = 0; opt.SelectedIndexChanged += (s, a) => { var pos = Camera.FocusPoint; switch (opt.SelectedIndex) { case 1: Camera.ResetView(v4.XAxis); break; case 2: Camera.ResetView(-v4.XAxis); break; case 3: Camera.ResetView(v4.YAxis); break; case 4: Camera.ResetView(-v4.YAxis); break; case 5: Camera.ResetView(v4.ZAxis); break; case 6: Camera.ResetView(-v4.ZAxis); break; case 7: Camera.ResetView(-v4.XAxis - v4.YAxis - v4.ZAxis); break; } Camera.FocusPoint = pos; Refresh(); }; } { // Object Manager UI //var obj_mgr_ui = new ToolStripMenuItem("Object Manager"); //view_menu.DropDownItems.Add(obj_mgr_ui); //obj_mgr_ui.Click += (s,a) => Window.ShowObjectManager(true); } } { // Navigation var rdr_menu = context_menu.Items.Add2(new ToolStripMenuItem("Navigation") { Name = CMenuItems.Navigation }); { // Reset View var opt = rdr_menu.DropDownItems.Add2(new ToolStripMenuItem("Reset View") { Name = CMenuItems.NavMenu.ResetView }); opt.Click += (s, a) => { Camera.ResetView(); Refresh(); }; } { // Align to var align_menu = rdr_menu.DropDownItems.Add2(new ToolStripMenuItem("Align") { Name = CMenuItems.NavMenu.Align }); { var opt = align_menu.DropDownItems.Add2(new ToolStripComboBox("Aligns") { Name = CMenuItems.NavMenu.AlignMenu.Aligns, DropDownStyle = ComboBoxStyle.DropDownList }); opt.Items.Add("None"); opt.Items.Add("X"); opt.Items.Add("Y"); opt.Items.Add("Z"); var axis = Camera.AlignAxis; if (Math_.FEql(axis, v4.XAxis)) { opt.SelectedIndex = 1; } else if (Math_.FEql(axis, v4.YAxis)) { opt.SelectedIndex = 2; } else if (Math_.FEql(axis, v4.ZAxis)) { opt.SelectedIndex = 3; } else { opt.SelectedIndex = 0; } opt.SelectedIndexChanged += (s, a) => { switch (opt.SelectedIndex) { default: Camera.AlignAxis = v4.Zero; break; case 1: Camera.AlignAxis = v4.XAxis; break; case 2: Camera.AlignAxis = v4.YAxis; break; case 3: Camera.AlignAxis = v4.ZAxis; break; } Refresh(); }; } } { // Motion lock } { // Orbit //var orbit_menu = new ToolStripMenuItem("Orbit"); //rdr_menu.DropDownItems.Add(orbit_menu); //orbit_menu.Click += delegate {}; } } { // Tools var tools_menu = context_menu.Items.Add2(new ToolStripMenuItem("Tools") { Name = CMenuItems.Tools }); { // Measure var opt = tools_menu.DropDownItems.Add2(new ToolStripMenuItem("Measure...") { Name = CMenuItems.ToolsMenu.Measure }); opt.Click += (s, a) => { ShowMeasurementUI = true; }; } { // Angle var opt = tools_menu.DropDownItems.Add2(new ToolStripMenuItem("Angle...") { Name = CMenuItems.ToolsMenu.Angle }); opt.Click += (s, a) => { Window.ShowAngleTool = true; }; } } { // Rendering var rdr_menu = context_menu.Items.Add2(new ToolStripMenuItem("Rendering") { Name = CMenuItems.Rendering }); { // Solid/Wireframe/Solid+Wire var opt = rdr_menu.DropDownItems.Add2(new ToolStripComboBox { Name = CMenuItems.RenderingMenu.FillMode, DropDownStyle = ComboBoxStyle.DropDownList }); opt.Items.AddRange(Enum <View3d.EFillMode> .Names.Cast <object>().ToArray()); opt.SelectedIndex = (int)Window.FillMode; opt.SelectedIndexChanged += (s, a) => { Window.FillMode = (View3d.EFillMode)opt.SelectedIndex; Refresh(); }; } { // Render2D var opt = rdr_menu.DropDownItems.Add2(new ToolStripMenuItem(Window.Camera.Orthographic ? "Perspective" : "Orthographic") { Name = CMenuItems.RenderingMenu.Orthographic }); opt.Click += (s, a) => { var _2d = Window.Camera.Orthographic; Window.Camera.Orthographic = !_2d; opt.Text = _2d ? "Perspective" : "Orthographic"; Refresh(); }; } { // Lighting... var opt = rdr_menu.DropDownItems.Add2(new ToolStripMenuItem("Lighting...") { Name = CMenuItems.RenderingMenu.Lighting }); opt.Click += (s, a) => { Window.ShowLightingDlg(); }; } { // Background colour var bk_colour_menu = rdr_menu.DropDownItems.Add2(new ToolStripMenuItem("Background Colour") { Name = CMenuItems.RenderingMenu.Background }); var opt = bk_colour_menu.DropDownItems.Add2(new ToolStripButton(" ")); opt.AutoToolTip = false; opt.BackColor = Window.BackgroundColour; opt.Click += (s, a) => { var cd = new ColourUI(); if (cd.ShowDialog() == DialogResult.OK) { opt.BackColor = cd.Colour; } }; opt.BackColorChanged += (s, a) => { Window.BackgroundColour = opt.BackColor; Refresh(); }; } } // Allow users to add custom menu options to the context menu // Do this last so that users have the option of removing options they don't want displayed OnCustomiseContextMenu(new CustomContextMenuEventArgs(context_menu)); context_menu.Show(MousePosition); }
protected override void OnPaint(PaintEventArgs e) { var gfx = e.Graphics; var dim = LayoutDimensions; if (dim.Empty) { base.OnPaint(e); return; } // Draw the wheel if ((Parts & EParts.Wheel) != 0) { var bm = WheelBitmap; gfx.DrawImageUnscaled(bm, 0, 0); // Draw the colour selection if ((Parts & EParts.ColourSelection) != 0) { var pt = WheelPoint(HSVColour); gfx.DrawEllipse(Pens.Black, pt.X - 2f, pt.Y - 2f, 4f, 4f); } } gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; // Draw the VSlider if ((Parts & EParts.VSlider) != 0) { gfx.DrawString(ValueLabel, SystemFonts.DefaultFont, Brushes.Black, dim.VLabel); if (!dim.VSlider.IsEmpty) { using (var b = new LinearGradientBrush( VerticalLayout ? dim.VSlider.LeftCentre() : dim.VSlider.BottomCentre(), VerticalLayout ? dim.VSlider.RightCentre() : dim.VSlider.TopCentre(), Color.Black, Color.White)) gfx.FillRectangle(b, dim.VSlider); gfx.DrawRectangle(Pens.Black, dim.VSlider); } // Draw the brightness selection if ((Parts & EParts.VSelection) != 0) { var v = VerticalLayout ? Math_.Lerp(dim.VSlider.Left, dim.VSlider.Right, (double)HSVColour.V) : Math_.Lerp(dim.VSlider.Bottom, dim.VSlider.Top, (double)HSVColour.V); var pts = SliderSelector(v, dim.VSlider, VerticalLayout); gfx.DrawLines(Pens.Black, pts); } } // Draw the ASlider if ((Parts & EParts.ASlider) != 0) { gfx.DrawString(AlphaLabel, SystemFonts.DefaultFont, Brushes.Black, dim.ALabel); if (!dim.ASlider.IsEmpty) { using (var b = new LinearGradientBrush( VerticalLayout ? dim.ASlider.LeftCentre() : dim.ASlider.BottomCentre(), VerticalLayout ? dim.ASlider.RightCentre() : dim.ASlider.TopCentre(), Color.Black, Color.White)) gfx.FillRectangle(b, dim.ASlider); gfx.DrawRectangle(Pens.Black, dim.ASlider); } // Draw the alpha selection if ((Parts & EParts.ASelection) != 0) { var v = VerticalLayout ? Math_.Lerp(dim.ASlider.Left, dim.ASlider.Right, (double)HSVColour.A) : Math_.Lerp(dim.ASlider.Bottom, dim.ASlider.Top, (double)HSVColour.A); var pts = SliderSelector(v, dim.ASlider, VerticalLayout); gfx.DrawLines(Pens.Black, pts); } } }
public static string Vec4(v4 vec) { Debug.Assert(Math_.IsFinite(vec)); return($"{vec.x} {vec.y} {vec.z} {vec.w}"); }
public static string Mat4x4(m4x4 mat) { Debug.Assert(Math_.IsFinite(mat)); return($"{Vec4(mat.x)} {Vec4(mat.y)} {Vec4(mat.z)} {Vec4(mat.w)}"); }
public static float Length2Sq(this SizeF s) { return(Math_.Sqr(s.Width) + Math_.Sqr(s.Height)); }
public static float Length2(this SizeF s) { return(Math_.Sqrt(s.Length2Sq())); }
public static float Area(this SizeF s) { return(Math_.SignF(s.Width >= 0 && s.Height >= 0) * Math.Abs(s.Width * s.Height)); }
/// <summary>Returns the signed area. Returns a negative value if Width and/or Height are negative</summary> public static int Area(this Size s) { return(Math_.SignI(s.Width >= 0 && s.Height >= 0) * Math.Abs(s.Width * s.Height)); }
public static float Length2(this PointF p) { return(Math_.Sqrt(p.Length2Sq())); }
/// <summary> /// Determine if executing trades in 'loop' should result in a profit. /// Returns true if profitable and a copy of this loop in 'loop'</summary> public bool IsProfitable(bool forward, Fund fund, out Loop loop) { // How to think about this: // - We want to see what happens if we convert some currency to each of the coins // in the loop, ending up back at the initial currency. If the result is more than // we started with, then it's a profitable loop. // - We can go in either direction around the loop. // - We want to execute each trade around a profitable loop at the same time, so we're // limited to the smallest balance for the coins in the loop. // - The rate by volume does not depend on our account balance. We calculate the effective // rate at each of the offered volumes then determine if any of those volumes are profitable // and whether we have enough balance for the given volumes. // - The 'Bid' table contains the amounts of base currency people want to buy, ordered by price. // - The 'Ask' table contains the amounts of base currency people want to sell, ordered by price. loop = null; // Construct an "order book" of volumes and complete-loop prices (e.g. BTC to BTC price for each volume) var dir = forward ? +1 : -1; var coin = forward ? Beg : End; var tt = forward ? ETradeType.B2Q : ETradeType.Q2B; var obk = new OrderBook(coin, coin, tt) { new Offer(1m, decimal.MaxValue._(coin.Symbol)) }; foreach (var pair in EnumPairs(dir)) { // Limit the volume calculated, there's no point in calculating large volumes if we can't trade them Unit <decimal> bal = 0m; OrderBook b2q = null, q2b = null; using (Task_.NoSyncContext()) { Misc.RunOnMainThread(() => { bal = coin.Balances[fund].Available; b2q = new OrderBook(pair.MarketDepth.B2Q); q2b = new OrderBook(pair.MarketDepth.Q2B); }).Wait(); } // Note: the trade prices are in quote currency if (pair.Base == coin) { obk = MergeRates(obk, b2q, bal, invert: false); } else if (pair.Quote == coin) { obk = MergeRates(obk, q2b, bal, invert: true); } else { throw new Exception($"Pair {pair} does not include Coin {coin}. Loop is invalid."); } // Get the next coin in the loop coin = pair.OtherCoin(coin); } if (obk.Count == 0) { return(false); } // Save the best profit ratio for this loop (as an indication) if (forward) { ProfitRatioFwd = obk[0].PriceQ2B; } else { ProfitRatioBck = obk[0].PriceQ2B; } // Look for any volumes that have a nett gain var amount_gain = obk.Where(x => x.PriceQ2B > 1).Sum(x => x.PriceQ2B * x.AmountBase); if (amount_gain == 0) { return(false); } // Create a copy of the loop for editing (with the direction set) loop = new Loop(this, obk, dir); // Find the maximum profitable volume to trade var amount = 0m._(loop.Beg); foreach (var ordr in loop.Rate.Where(x => x.PriceQ2B > 1)) { amount += ordr.AmountBase; } // Calculate the effective fee in initial coin currency. // Do all trades assuming no fee, but accumulate the fee separately var fee = 0m._(loop.Beg); var initial_volume = amount; // Trade each pair in the loop (in the given direction) to check // that the trade is still profitable after fees. Record each trade // so that we can determine the trade scale coin = loop.Beg; var trades = new List <Trade>(); foreach (var pair in loop.EnumPairs(loop.Direction)) { // If we trade 'volume' using 'pair' that will result in a new volume // in the new currency. There will also be a fee charged (in quote currency). // If we're trading to quote currency, the new volume is reduced by the fee. // If we're trading to base currency, the cost is increased by the fee. // Calculate the result of the trade var new_coin = pair.OtherCoin(coin); var trade = pair.Base == coin ? pair.BaseToQuote(fund, amount) : pair.QuoteToBase(fund, amount); // Record the trade amount. trades.Add(trade); // Convert the fee so far to the new coin using the effective rate, // and add on the fee for this trade. var rate = trade.AmountOut / trade.AmountIn; fee = fee * rate + trade.AmountOut * pair.Fee; // Advance to the next pair coin = new_coin; amount = trade.AmountOut; } // Record the volume to trade, the scale, and the expected profit. // If the new volume is greater than the initial volume, WIN! // Update the profitability of the loop now we've accounted for fees. loop.TradeScale = 1m; loop.Tradeability = string.Empty; loop.TradeVolume = initial_volume; loop.Profit = (amount - fee) - initial_volume; if (forward) { loop.ProfitRatioFwd = ProfitRatioFwd = (amount - fee) / initial_volume; } else { loop.ProfitRatioBck = ProfitRatioBck = (amount - fee) / initial_volume; } if (loop.ProfitRatio <= 1m) { return(false); } // Determine the trade scale based on the available balances foreach (var trade in trades) { var pair = trade.Pair; // Get the balance available for this trade and determine a trade scaling factor. // Increase the required volume to allow for the fee // Reduce the available balance slightly to deal with rounding errors var bal = trade.CoinIn.Balances[fund].Available * 0.999m; var req = trade.AmountIn * (1 + pair.Fee); var scale = Math_.Clamp((decimal)(bal / req), 0m, 1m); if (scale < loop.TradeScale) { loop.TradeScale = Math_.Clamp(scale, 0, loop.TradeScale); loop.LimitingCoin = trade.CoinIn; } } // Check that all traded volumes are within the limits var all_trades_valid = EValidation.Valid; foreach (var trade in trades) { // Check the unscaled amount, if that's too small we'll ignore this loop var validation0 = trade.Validate(); all_trades_valid |= validation0; // Record why the base trade isn't valid if (validation0 != EValidation.Valid) { loop.Tradeability += $"{trade.Description} - {validation0}\n"; } // If the volume to trade, multiplied by the trade scale, is outside the // allowed range of trading volume, set the scale to zero. This is to prevent // loops being traded where part of the loop would be rejected. var validation1 = new Trade(trade, loop.TradeScale).Validate(); if (validation1.HasFlag(EValidation.AmountInOutOfRange)) { loop.Tradeability += $"Not enough {trade.CoinIn} to trade\n"; } if (validation1.HasFlag(EValidation.AmountOutOutOfRange)) { loop.Tradeability += $"Trade result volume of {trade.CoinOut} is too small\n"; } if (validation1 != EValidation.Valid) { loop.TradeScale = 0m; } } // Return the profitable loop (even if scaled to 0) return(all_trades_valid == EValidation.Valid); }
public static bool FEqlRelative(Size lhs, Size rhs, double tol) { return (Math_.FEqlRelative(lhs.Width, rhs.Width, tol) && Math_.FEqlRelative(lhs.Height, rhs.Height, tol)); }
/// <summary>Set up the text box fields</summary> private void SetupFields() { // Validation CancelEventHandler Validating = (s, a) => { a.Cancel = !byte.TryParse(((TextBox)s).Text, out var dummy); }; m_edit_red.Validating += Validating; m_edit_green.Validating += Validating; m_edit_blue.Validating += Validating; m_edit_alpha.Validating += Validating; m_edit_hue.Validating += Validating; m_edit_sat.Validating += Validating; m_edit_lum.Validating += Validating; // Accept value Action <byte?, byte?, byte?, byte?> SetARGB = (a, r, g, b) => { var c = m_wheel.Colour; m_wheel.Colour = Color.FromArgb(a ?? c.A, r ?? c.R, g ?? c.G, b ?? c.B); }; Action <float?, float?, float?, float?> SetAHSV = (a, h, s, v) => { var c = m_wheel.HSVColour; if (a.HasValue) { a = Math_.Clamp(a.Value / 255f, 0f, 1f); } if (h.HasValue) { h = Math_.Clamp(h.Value / 255f, 0f, 1f); } if (s.HasValue) { s = Math_.Clamp(s.Value / 255f, 0f, 1f); } if (v.HasValue) { v = Math_.Clamp(v.Value / 255f, 0f, 1f); } m_wheel.HSVColour = HSV.FromAHSV(a ?? c.A, h ?? c.H, s ?? c.S, v ?? c.V); }; m_edit_alpha.Validated += (s, a) => SetARGB(byte.Parse(((TextBox)s).Text), null, null, null); m_edit_red.Validated += (s, a) => SetARGB(null, byte.Parse(((TextBox)s).Text), null, null); m_edit_green.Validated += (s, a) => SetARGB(null, null, byte.Parse(((TextBox)s).Text), null); m_edit_blue.Validated += (s, a) => SetARGB(null, null, null, byte.Parse(((TextBox)s).Text)); m_edit_hue.Validated += (s, a) => SetAHSV(null, byte.Parse(((TextBox)s).Text), null, null); m_edit_sat.Validated += (s, a) => SetAHSV(null, null, byte.Parse(((TextBox)s).Text), null); m_edit_lum.Validated += (s, a) => SetAHSV(null, null, null, byte.Parse(((TextBox)s).Text)); m_edit_hex.Validating += (s, a) => { a.Cancel = !uint.TryParse(((TextBox)s).Text, NumberStyles.HexNumber, null, out var dummy); }; m_edit_hex.Validated += (s, a) => { var argb = uint.Parse(((TextBox)s).Text, NumberStyles.HexNumber); unchecked { m_wheel.Colour = Color.FromArgb((int)argb); } }; }
/// <summary>Compare sizes for approximate equality</summary> public static bool FEql(Point lhs, Point rhs) { return (Math_.FEql(lhs.X, rhs.X) && Math_.FEql(lhs.Y, rhs.Y)); }
/// <summary>The distance from x1,y1 to x2,y2</summary> public static double Distance(double x1, double y1, double x2, double y2) { return(Math_.Sqrt(Math_.Sqr(x2 - x1) + Math_.Sqr(y2 - y1))); }
public static bool FEqlRelative(Point lhs, Point rhs, double tol) { return (Math_.FEqlRelative(lhs.X, rhs.X, tol) && Math_.FEqlRelative(lhs.Y, rhs.Y, tol)); }
/// <summary>Update the derived fields from the given spec</summary> public void UseSpec(ShipSpec spec) { Spec = spec; // Determine the size and mass of the ship FuelMass = 0; FuelVolume = 0; FuelTankMass = 0; double tank_volume = 0; foreach (var f in spec.Fuel) { double v; FuelMass += f.Mass; FuelVolume += v = f.Volume(Consts, World); FuelTankMass += f.TankMass(v, World); tank_volume += f.TankAndFuelVolume(v, World); } PassengerMass = Spec.PassengerCount * Consts.AveragePassengerWeight; double passenger_volume = Spec.PassengerCount * Consts.AveragePassengerPersonalSpace; // Assume the ship is a spherical ball housing the passengers only double xs_area = Consts.CabinPressure / Spec.HullCompound.Strength; ; double ship_inner = Math_.SphereRadius(passenger_volume); double ship_outer = Math_.Sqrt(2.0 * xs_area / Math_.Tau + Math_.Sqr(ship_inner)); var hull_volume = (2.0 / 3.0) * Math_.Tau * (Math_.Cubed(ship_outer) - Math_.Cubed(ship_inner)); HullMass = hull_volume * Spec.HullCompound.Density(World.AverageLocalTemperature, 0.0); TotalVolume = hull_volume + tank_volume; TotalInitialMass = HullMass + FuelTankMass + PassengerMass; // Construction time is a function of how big the ship is ConstructionTime = TotalVolume / Consts.ShipConstructionRate; }
/// <summary>Compare sizes for approximate equality</summary> public static bool FEql(Vector lhs, Vector rhs) { return (Math_.FEql(lhs.X, rhs.X) && Math_.FEql(lhs.Y, rhs.Y)); }
/// <summary>Round parameters to match the server rules</summary> public OrderParams Canonicalise(CurrencyPair pair, BinanceApi api) { // Canonicalise doesn't throw, it just does it's best. // Use Validate to get error messages. // Find the rules for 'cp'. Valid if no rules found var rules = api.SymbolRules[pair]; if (rules == null) { return(this); } var ticker = api.TickerData[pair]; if (Type == EOrderType.MARKET) { PriceQ2B = ticker.PriceQ2B; } if (!Type.IsAlgo()) { StopPriceQ2B = null; } else if (StopPriceQ2B == null) { StopPriceQ2B = PriceQ2B; } // Truncate to the expected precision. Can't round because we might round to a value greater than the balance AmountBase = Math_.Truncate(AmountBase, rules.BaseAssetPrecision); if (PriceQ2B != null) { PriceQ2B = Math_.Truncate(PriceQ2B.Value, rules.PricePrecision); } if (StopPriceQ2B != null) { StopPriceQ2B = Math_.Truncate(StopPriceQ2B.Value, rules.PricePrecision); } if (IcebergAmountBase != null) { IcebergAmountBase = Math_.Truncate(IcebergAmountBase.Value, rules.BaseAssetPrecision); } // Round to the tick size foreach (var filter in rules.Filters.OfType <ServerRulesData.FilterPrice>().Where(x => x.FilterType == EFilterType.PRICE_FILTER)) { if (PriceQ2B != null) { PriceQ2B = filter.Round(PriceQ2B.Value); } if (StopPriceQ2B != null) { StopPriceQ2B = filter.Round(StopPriceQ2B.Value); } } // Round to the lot size var filter_type = Type != EOrderType.MARKET ? EFilterType.LOT_SIZE : EFilterType.MARKET_LOT_SIZE; foreach (var filter in rules.Filters.OfType <ServerRulesData.FilterLotSize>().Where(x => x.FilterType == filter_type)) { AmountBase = filter.Round(AmountBase); if (IcebergAmountBase != null) { IcebergAmountBase = filter.Round(IcebergAmountBase.Value); } } // Test against min notional foreach (var filter in rules.Filters.OfType <ServerRulesData.FilterMinNotional>().Where(x => x.FilterType == EFilterType.MIN_NOTIONAL)) { if (Type == EOrderType.MARKET && !filter.ApplyToMarketOrders) { continue; } AmountBase = filter.Round(AmountBase, PriceQ2B.Value); } return(this); }
public static bool FEqlRelative(Vector lhs, Vector rhs, double tol) { return (Math_.FEqlRelative(lhs.X, rhs.X, tol) && Math_.FEqlRelative(lhs.Y, rhs.Y, tol)); }
/// <summary>Returns the volume of the tank including the fuel</summary> public double TankAndFuelVolume(double fuel_volume, WorldState world) { return(Math_.SphereRadius(fuel_volume) + TankWallThickness(fuel_volume)); }
/// <summary>Compare sizes for approximate equality</summary> public static bool FEql(Size lhs, Size rhs) { return (Math_.FEql(lhs.Width, rhs.Width) && Math_.FEql(lhs.Height, rhs.Height)); }
public GameConstants(int seed, bool real_chemistry) { GameSeed = seed; ElementCount = ElementNames.Length; // Universal constants MaxGameDuration = 30 * 60 * 60; // 30 minutes InitialTimeTillNova = 365 * 24 * 60 * 60; InitialTimeTillNovaErrorMargin = 20 * 24 * 60 * 60; m_time_scaler = InitialTimeTillNova / MaxGameDuration; m_speed_of_light = 2.99792458e8; m_gravitational_constant = 6.6738e-11; CoulombConstant = 8.987551e9; ElectronCharge = 1.60217657e-19; ProtonMass = 1.67262178e-27; GasConstant = 8.3144621; MaxMolarMass = 2.0 * ElementCount; MinElectronegativity = 0.7; MaxElectronegativity = 4.0; MinSolidMaterialDensity = 350.0; MaxSolidMaterialDensity = 25000.0; var rnd = new Random(GameSeed); ValenceLevels = new int[10]; if (real_chemistry) { StableShellCount = 8; // The total numbers of electrons at each orbital level ValenceLevels[0] = 0; ValenceLevels[1] = 2; ValenceLevels[2] = 10; ValenceLevels[3] = 18; ValenceLevels[4] = 36; ValenceLevels[5] = 54; ValenceLevels[6] = 86; ValenceLevels[7] = 118; } else { StableShellCount = rnd.Next(6, 11); // [6,10] // The total numbers of electrons at each orbital level ValenceLevels[0] = 0; ValenceLevels[1] = rnd.Next(1, 4); for (int i = 2; i != ValenceLevels.Length; ++i) { int v = 1 + ValenceLevels[i - 1]; ValenceLevels[i] = (int)rnd.Double(1.3 * v, 2.9 * v); } } OrbitalRadii = new Range[ValenceLevels.Length]; OrbitalRadii[0] = new Range(0.0, 0.0); OrbitalRadii[1] = new Range(31.0, 53.0); OrbitalRadii[2] = new Range(38.0, 167.0); OrbitalRadii[3] = new Range(71.0, 190.0); OrbitalRadii[4] = new Range(88.0, 243.0); OrbitalRadii[5] = new Range(108.0, 265.0); OrbitalRadii[6] = new Range(120.0, 298.0); OrbitalRadii[7] = new Range(132.0, 341.0); OrbitalRadii[8] = new Range(140.0, 390.0); OrbitalRadii[9] = new Range(144.0, 450.0); // Pick a star mass approximately the same as the sun const double suns_mass = 2.0e30; StarMass = rnd.DoubleC(suns_mass, suns_mass * 0.25); // Pick a distance from the star, somewhere between mercury and mars const double sun_to_mercury = 5.79e10; const double sun_to_mars = 2.279e11; StarDistance = rnd.Double(sun_to_mercury, sun_to_mars); // The acceleration due to the star's gravity at the given distance m_star_gravitational_acceleration = m_gravitational_constant * StarMass / Math_.Sqr(StarDistance); // Calculate the required escape velocity (speed) // Escape Velocity = Sqrt(2 * G * M / r), G = 6.67x10^-11 m³kg^-1s^-2, M = star mass, r = distance from star EscapeVelocity = Math_.Sqrt(2.0 * m_gravitational_constant * StarMass / StarDistance); // Set up per passenger constants AveragePassengerWeight = rnd.DoubleC(80.0, 10.0); AveragePassengerPersonalSpace = rnd.DoubleC(2.0, 0.5); CabinPressure = 1000; // The total number of people available to work m_total_man_power = rnd.IntC(10000, 0); // The ship is roughly 10% bigger than the volume of it's contents ShipVolumeScaler = rnd.DoubleC(1.11, 0.0); ShipConstructionRate = rnd.DoubleC(0.1, 0.0); // The total man days needed to discover the star mass m_star_mass_discovery_effort = rnd.DoubleC(1000, 0.0); // The rate at which the star distance can be discovered proportional to the main hours assigned m_star_distance_discovery_effort = rnd.DoubleC(1000, 0.0); }
/// <summary>Generates the boundary of the hint balloon</summary> private GraphicsPath GeneratePath(bool region_border) { var width = Width + (region_border ? 1 : 0); var height = Height + (region_border ? 1 : 0); var cr = Math.Max(1, CornerRadius); GraphicsPath path; if (TipLength == 0 || TipBaseWidth == 0) { path = Gdi.RoundedRectanglePath(new RectangleF(0, 0, width, height), cr); } else { var tip_length = TipLength; var tip_width = Math_.Clamp(width - 2 * (tip_length + cr), Math.Min(5, TipBaseWidth), TipBaseWidth); // Find the corner to start from path = new GraphicsPath(); switch (TipCorner) { default: Debug.Assert(false, "Unknown corner"); break; case ETipCorner.TopLeft: path.AddLine(0, 0, tip_length + cr + tip_width, tip_length); path.AddArc(width - tip_length - cr, tip_length, cr, cr, 270f, 90f); path.AddArc(width - tip_length - cr, height - tip_length - cr, cr, cr, 0f, 90f); path.AddArc(tip_length, height - tip_length - cr, cr, cr, 90f, 90f); path.AddArc(tip_length, tip_length, cr, cr, 180f, 90f); path.AddLine(tip_length + cr, tip_length, 0, 0); break; case ETipCorner.TopRight: path.AddLine(width, 0, width - tip_length - cr, tip_length); path.AddArc(width - tip_length - cr, tip_length, cr, cr, 270f, 90f); path.AddArc(width - tip_length - cr, height - tip_length - cr, cr, cr, 0f, 90f); path.AddArc(tip_length, height - tip_length - cr, cr, cr, 90f, 90f); path.AddArc(tip_length, tip_length, cr, cr, 180f, 90f); path.AddLine(tip_length + cr, tip_length, width - tip_length - cr - tip_width, tip_length); path.AddLine(width - tip_length - cr - tip_width, tip_length, width, 0); break; case ETipCorner.BottomLeft: path.AddLine(0, height, tip_length + cr, height - tip_length); path.AddArc(tip_length, height - tip_length - cr, cr, cr, 90f, 90f); path.AddArc(tip_length, tip_length, cr, cr, 180f, 90f); path.AddArc(width - tip_length - cr, tip_length, cr, cr, 270f, 90f); path.AddArc(width - tip_length - cr, height - tip_length - cr, cr, cr, 0f, 90f); path.AddLine(width - tip_length - cr, height - tip_length, tip_length + cr + tip_width, height - tip_length); path.AddLine(tip_length + cr + tip_width, height - tip_length, 0, height); break; case ETipCorner.BottomRight: path.AddLine(width, height, width - tip_length - cr - tip_width, height - tip_length); path.AddArc(tip_length, height - tip_length - cr, cr, cr, 90f, 90f); path.AddArc(tip_length, tip_length, cr, cr, 180f, 90f); path.AddArc(width - tip_length - cr, tip_length, cr, cr, 270f, 90f); path.AddArc(width - tip_length - cr, height - tip_length - cr, cr, cr, 0f, 90f); path.AddLine(width - tip_length - cr, height - tip_length, width, height); break; } } return(path); }
public static string Vec3(v2 vec) { Debug.Assert(Math_.IsFinite(vec)); return($"{vec.x} {vec.y} 0"); }
/// <summary>The grunt work of building the new line index.</summary> private static void BuildLineIndexAsync(BLIData d, Action <BLIData, RangeI, List <RangeI>, Exception> on_complete) { // This method runs in a background thread // All we're doing here is loading data around 'd.filepos' so that there are an equal number // of lines on either side. This can be optimised however because the existing range of // cached data probably overlaps the range we want loaded. try { Log.Write(ELogLevel.Info, "BLIAsync", $"build started. (id {d.build_issue}, reload {d.reload})"); if (BuildCancelled(d.build_issue)) { return; } using (d.file) { // A temporary buffer for reading sections of the file var buf = new byte[d.max_line_length]; // Seek to the first line that starts immediately before 'filepos' d.filepos = FindLineStart(d.file, d.filepos, d.fileend, d.row_delim, d.encoding, buf); if (BuildCancelled(d.build_issue)) { return; } // Determine the range to scan and the number of lines in each direction var scan_backward = (d.fileend - d.filepos) > (d.filepos - 0); // scan in the most bound direction first var scan_range = CalcBufferRange(d.filepos, d.fileend, d.file_buffer_size); var line_range = CalcLineRange(d.line_cache_count); var bwd_lines = line_range.Begi; var fwd_lines = line_range.Endi; // Incremental loading - only load what isn't already cached. // If the 'filepos' is left of the cache centre, try to extent in left direction first. // If the scan range in that direction is empty, try extending at the other end. The // aim is to try to get d.line_index_count as close to d.line_cache_count as possible // without loading data that is already cached. #region Incremental loading if (!d.reload && !d.cached_whole_line_range.Empty) { // Determine the direction the cached range is moving based on where 'filepos' is relative // to the current cache centre and which range contains an valid area to be scanned. // With incremental scans we can only update one side of the cache because the returned line index has to // be a contiguous block of lines. This means one of 'bwd_lines' or 'fwd_lines' must be zero. var Lrange = new RangeI(scan_range.Beg, d.cached_whole_line_range.Beg); var Rrange = new RangeI(d.cached_whole_line_range.End, scan_range.End); var dir = (!Lrange.Empty && !Rrange.Empty) ? Math.Sign(2 * d.filepos_line_index - d.line_cache_count) : (!Lrange.Empty) ? -1 : (!Rrange.Empty) ? +1 : 0; // Determine the number of lines to scan, based on direction if (dir < 0) { scan_backward = true; scan_range = Lrange; bwd_lines -= Math_.Clamp(d.filepos_line_index - 0, 0, bwd_lines); fwd_lines = 0; } else if (dir > 0) { scan_backward = false; scan_range = Rrange; bwd_lines = 0; fwd_lines -= Math_.Clamp(d.line_index_count - d.filepos_line_index - 1, 0, fwd_lines); } else if (dir == 0) { bwd_lines = 0; fwd_lines = 0; scan_range = RangeI.Zero; } } #endregion Debug.Assert(bwd_lines + fwd_lines <= d.line_cache_count); // Build the collection of line byte ranges to add to the cache var line_index = new List <RangeI>(); if (bwd_lines != 0 || fwd_lines != 0) { // Line index buffers for collecting the results var fwd_line_buf = new List <RangeI>(); var bwd_line_buf = new List <RangeI>(); // Data used in the 'add_line' callback. Updated for forward and backward passes var lbd = new LineBufferData { line_buf = null, // pointer to either 'fwd_line_buf' or 'bwd_line_buf' line_limit = 0, // Caps the number of lines read for each of the forward and backward searches }; // Callback for adding line byte ranges to a line buffer AddLineFunc add_line = (line, baddr, fend, bf, enc) => { if (line.Empty && d.ignore_blanks) { return(true); } // Test 'text' against each filter to see if it's included // Note: not caching this string because we want to read immediate data // from the file to pick up file changes. string text = d.encoding.GetString(buf, (int)line.Beg, (int)line.Size); if (!PassesFilters(text, d.filters)) { return(true); } // Convert the byte range to a file range line = line.Shift(baddr); Debug.Assert(new RangeI(0, d.fileend).Contains(line)); lbd.line_buf.Add(line); Debug.Assert(lbd.line_buf.Count <= lbd.line_limit); return((fwd_line_buf.Count + bwd_line_buf.Count) < lbd.line_limit); }; // Callback for updating progress ProgressFunc progress = (scanned, length) => { int numer = fwd_line_buf.Count + bwd_line_buf.Count, denom = lbd.line_limit; return(d.progress(numer, denom) && !BuildCancelled(d.build_issue)); }; // Scan twice, starting in the direction of the smallest range so that any // unused cache space is used by the search in the other direction var scan_from = Math_.Clamp(d.filepos, scan_range.Beg, scan_range.End); for (int a = 0; a != 2; ++a, scan_backward = !scan_backward) { if (BuildCancelled(d.build_issue)) { return; } lbd.line_buf = scan_backward ? bwd_line_buf : fwd_line_buf; lbd.line_limit += scan_backward ? bwd_lines : fwd_lines; if ((bwd_line_buf.Count + fwd_line_buf.Count) < lbd.line_limit) { var length = scan_backward ? scan_from - scan_range.Beg : scan_range.End - scan_from; FindLines(d.file, scan_from, d.fileend, scan_backward, length, add_line, d.encoding, d.row_delim, buf, progress); } } // Scanning backward adds lines to the line index in reverse order. bwd_line_buf.Reverse(); // 'line_index' should be a contiguous block of byte offset ranges for // the lines around 'd.filepos'. If 'd.reload' is false, then the line // index will only contain byte offset ranges that are not currently cached. line_index.Capacity = bwd_line_buf.Count + fwd_line_buf.Count; line_index.AddRange(bwd_line_buf); line_index.AddRange(fwd_line_buf); } // Job done on_complete(d, scan_range, line_index, null); } } catch (Exception ex) { on_complete(d, RangeI.Zero, null, ex); } }