sharplib/db/System.cs

309 lines
5.9 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using System.Threading;
using System.Diagnostics;
using Optional;
using System.Diagnostics.CodeAnalysis;
namespace db
{
struct TimedAction : IComparable<TimedAction>
{
public long when;
public Act act;
public TimedAction( long when, Act act )
{
this.when = when;
this.act = act;
}
public int CompareTo( TimedAction other )
{
return when.CompareTo( other.when );
}
public override bool Equals( object obj )
{
return obj is TimedAction action &&
when == action.when &&
EqualityComparer<Act>.Default.Equals( act, action.act );
}
public override int GetHashCode()
{
var hc = when.GetHashCode() ^ act.GetHashCode();
return hc;
}
}
public class SystemCfg : lib.Config
{
public readonly float Cores = 1;
}
public class System<TID, T> where T : IID<TID>
{
//public static System Current => s_system;
public SemaphoreSlim ActsExist => m_actsExist;
public DB<TID, T> DB { get; private set; }
public bool Running { get; private set; }
public System( res.Ref<SystemCfg> cfg, DB<TID, T> db )
{
m_cfg = cfg;
DB = db;
var procCount = Environment.ProcessorCount;
//Exact comparison
if( m_cfg.res.Cores != 0.0f )
{
//If its less than 1, then use it as a multiplier
if( m_cfg.res.Cores < 0.0f )
{
procCount = Environment.ProcessorCount - (int)m_cfg.res.Cores;
}
else if( m_cfg.res.Cores < 1.0f )
{
procCount = (int) ((float)Environment.ProcessorCount * m_cfg.res.Cores);
}
else
{
procCount = (int)m_cfg.res.Cores;
}
}
log.info( $"Running {procCount} cores out of a total cores {Environment.ProcessorCount} via a config Cores value of {m_cfg.res.Cores}" );
Processor<TID, T>[] procs = new Processor<TID, T>[procCount];
for( var i = 0; i < procCount; ++i )
{
var proc = new Processor<TID, T>( db, this );
procs[i] = proc;
}
m_processors = m_processors.AddRange( procs );
Running = true;
}
public void forcedThisTick( Act act )
{
m_current.Add( act );
m_actsExist.Release();
}
public void next( Act act )
{
m_next.Add( act );
}
//Most things dont need accurate next frame processing, so split them between the next frame N frames
const double s_variance = 1.0 / 15.0;
public void future( Act act, double future, double maxVariance = s_variance )
{
//m_actions.Add( act );
var variance = m_rand.NextDouble() * maxVariance;
var nextTime = future + variance;
if( nextTime < 1.0 / 60.0 )
{
next( act );
return;
}
var ts = TimeSpan.FromSeconds( nextTime );
var tsTicks = ts.Ticks;
// @@@ TIMING Should we use a fixed time at the front of the frame for this?
var ticks = tsTicks + DateTime.Now.Ticks;
var ta = new TimedAction( ticks, act );
var newFuture = m_futureActions.Add( ta );
Interlocked.Exchange( ref m_futureActions, newFuture );
}
public void start()
{
int count = 0;
foreach( var p in m_processors )
{
var start = new ThreadStart( p.run );
var th = new Thread( start );
th.Name = $"Processor_{count}";
th.Start();
++count;
}
}
public void tick()
{
//Debug.Assert( m_current.IsEmpty );
addTimedActions();
var current = m_current;
m_current = m_next;
m_next = current;
while( !m_current.IsEmpty )
{
m_actsExist.Release();
}
/*
foreach( var proc in m_processors )
{
//Debug.Assert( proc.State == State.Waiting );
proc.kick();
}
*/
}
/*
public void wait_blah( int targetMs, int maxMs )
{
var done = 0;
var start = DateTime.Now;
var delta = start - start;
while( done < m_processors.Count && delta.TotalMilliseconds < maxMs )
{
done = 0;
foreach( var proc in m_processors )
{
if( proc.State != State.Active )
{
++done;
}
}
delta = DateTime.Now - start;
}
if( done != m_processors.Count )
{
log.warn( $"Processing took significantly too long {delta.TotalSeconds}sec." );
foreach( var proc in m_processors )
{
Act debugAct = proc.DebugCurrentAct;
if( proc.State == State.Active )
{
log.warn( $"Proc is still running\n{debugAct.Path}({debugAct.Line}): In method {debugAct.Member}" );
// @@@ TODO Should we kill the procedure? Let it continue to run?
}
}
}
if( delta.TotalMilliseconds > targetMs )
{
log.warn( $"Missed our target {delta.TotalMilliseconds} framerate." );
}
}
//*/
public void addTimedActions()
{
var sortedFutureActions = m_futureActions.Sort( );
var future = TimeSpan.FromMilliseconds( 33.33333 );
var time = DateTime.Now + future;
foreach( var action in sortedFutureActions )
{
if( action.when < time.Ticks )
{
next( action.act );
var newActions = m_futureActions.Remove( action );
Interlocked.Exchange( ref m_futureActions, newActions );
}
else
{
break;
}
}
}
public void stopRunning()
{
Running = false;
}
internal Option<Act> getNextAct()
{
if( m_current.TryTake( out Act res ) )
{
return res.Some();
}
m_actsExist.Wait();
return Option.None<Act>();
}
res.Ref<SystemCfg> m_cfg;
SemaphoreSlim m_actsExist = new SemaphoreSlim(0);
Random m_rand = new Random();
ConcurrentBag<Act> m_current = new ConcurrentBag<Act>();
ConcurrentBag<Act> m_next = new ConcurrentBag<Act>();
// @@ TODO Keep an eye on the timing of this.
ImmutableList<TimedAction> m_futureActions = ImmutableList<TimedAction>.Empty;
/*
TimedAction[] m_sortedFutureActions = new TimedAction[16 * 1024];
int m_sfaStart = 0;
int m_sfaEnd = 0;
*/
ImmutableList<Processor<TID, T>> m_processors = ImmutableList<Processor<TID, T>>.Empty;
//private static System s_system;
}
}