using System; using System.Collections.Immutable; using Optional; using static Optional.OptionExtensions; using static System.Collections.Immutable.ImmutableInterlocked; /* ???? Should we have an explicit transaction class/ID? ???? Should we split things into threaded vs action */ namespace db; public enum CommitResults { Invalid, Perfect, Collisions, } public interface IID { TS id { get; } } public class DB where T : IID { object m_lock = new object(); //Current snapshot of the DB. ImmutableDictionary m_objs = ImmutableDictionary.Empty; //List of committed Ids based on when they were committed. ImmutableList m_committed = ImmutableList.Empty; ImmutableDictionary Objects => m_objs; public DB() { LogGC.RegisterObjectId( m_lock ); } // @@@@ TODO This returns an entity that can be changing. It should be a lazy instantiated copy public Option lookup( TID id ) { if( m_objs.TryGetValue( id, out T obj ) ) { return obj.Some(); } else { // LOG } return obj.None(); } public (Tx, Option) checkout( TID id ) { var tx = new Tx( m_committed.Count, m_activeTransaction, this ); var v = lookup( id ); v.Match( t => { tx.checkout( id ); }, () => { } ); return (tx, v); } public Tx checkout( TID id, out Option tOut ) { var (tx, v) = checkout(id); tOut = v; return tx; } public Tx checkout() { var tx = new Tx( m_committed.Count, m_activeTransaction, this ); return tx; } public CommitResults commit( ref Tx co ) { co = null; return commit_internal_single( co ); } public ImmutableDictionary getSnapshot() { ImmutableDictionary res = m_objs; return res; } internal CommitResults commit_internal_single( Tx tx ) { //var collision = false; //Check for previously committed things var start = tx.Start; var curCommitted = m_committed; foreach( var t in tx.Checkouts ) { for( int i = start; i < curCommitted.Count; ++i ) { if( !t.id.Equals( curCommitted[i] ) ) { } else { //collision = true; return CommitResults.Collisions; } } } // @@@@ LOCK lock( m_committed ) { TID[] committed = new TID[tx.Checkouts.Count]; for( var i = 0; i < tx.Checkouts.Count; ++i ) { committed[i] = tx.Checkouts[i].id; m_objs = m_objs.Add( tx.Checkouts[i].id, tx.Checkouts[i] ); } m_committed = m_committed.AddRange(committed); foreach( var v in tx.Adds ) { m_objs = m_objs.Add( v.id, v ); } return CommitResults.Perfect; } } Option> m_activeTransaction = Option.None>(); } public enum TxStates { Invalid, Running, Committed, } //This only works for a single thread public class Tx: IDisposable where T : IID { internal ImmutableList Checkouts => m_checkouts; internal TxStates State => m_state; internal int Start => m_start; internal ImmutableList Adds => m_adds; internal Tx( int start, DB db ) : this(start, Option.None>(), db) { } internal Tx( int start, Option> parentTx, DB db ) { m_start = start; m_parentTx = parentTx; m_childTx = m_childTx.Add(this); m_db = db; m_state = TxStates.Running; } public void Dispose() { // Dispose of unmanaged resources. Dispose( true ); // Suppress finalization. GC.SuppressFinalize( this ); } public void Dispose(bool isFromDispose ) { if( isFromDispose ) { m_db.commit_internal_single( this ); } } public Option checkout( TID id ) { var v = m_db.lookup( id ); v.MatchSome( t => { m_checkouts = m_checkouts.Add( t ); } ); return v; } public void add( T obj ) { m_adds = m_adds.Add(obj); } int m_start = -1; DB m_db; //Do we need these? Do we need both? Option> m_parentTx; ImmutableList> m_childTx = ImmutableList>.Empty; TxStates m_state = TxStates.Invalid; ImmutableList m_checkouts = ImmutableList.Empty; // New objects created this pass ImmutableList m_adds = ImmutableList.Empty; }