/* $Id$
*
* Project: Swicli.Library - Two Way Interface for .NET and MONO to SWI-Prolog
* Author: Douglas R. Miles
* E-mail: logicmoo@gmail.com
* WWW: http://www.logicmoo.com
* Copyright (C): 2010-2012 LogicMOO Developement
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*********************************************************/
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using SbsSW.SwiPlCs;
using SbsSW.SwiPlCs.Callback;
using SbsSW.SwiPlCs.Exceptions;
namespace Swicli.Library
{
public partial class PrologCLR
{
///
/// ?- current_bot(Obj),cli_add_event_handler(Obj,'EachSimEvent',c(A,format(user_error,'EV = ~q.~n',[A])),Out).
///
///
///
///
///
///
///
[NonDet(Arity = 4, ForeignSwitches = (PlForeignSwitches.Nondeterministic | PlForeignSwitches.VarArgs), DelegateType = typeof(DelegateParameterBacktrackVarArgs))]
internal static int cliAddEventHandler(PlTerm term1, int arity, IntPtr control)
{
return cliAddEventHandler0(term1, term1.Shift(1), term1.Shift(2), term1.Shift(3), control);
}
public static int cliAddEventHandler0(PlTerm clazzOrInstance, PlTerm memberSpec, PlTerm closureTerm, PlTerm blockOn, IntPtr control)
{
var handle = control;
FRG fc = (FRG)(libpl.PL_foreign_control(control));
switch (fc)
{
case FRG.PL_FIRST_CALL:
{
object getInstance;
Type c;
if (!GetInstanceAndType(clazzOrInstance, out getInstance, out c)) return PlSucceedOrFail(false);
Type[] paramz = null;
if (!CheckBound(memberSpec, closureTerm)) return PlSucceedOrFail(false);
EventInfo fi = findEventInfo(memberSpec, c, ref paramz, BindingFlagsALL);
if (fi == null)
{
return Embedded.Error("Cant find event {0} on {1}", memberSpec, (object)c ?? clazzOrInstance) ? 3 : 0;
}
ClosureDelegate newClosureDelegate = new ClosureDelegate(fi, getInstance, closureTerm);
var v = NondetContextHandle.ObtainHandle(control, newClosureDelegate);
bool res = v.Setup(new PlTermV(closureTerm, blockOn));
blockOn.FromObject(newClosureDelegate);
bool more = v.HasMore();
if (more)
{
libpl.PL_retry(v.Handle);
return res ? 3 : 0;
}
return res ? 1 : 0;
} break;
case FRG.PL_REDO:
{
var v = NondetContextHandle.FindHandle(control);
bool res = v.Call(new PlTermV(closureTerm, blockOn));
bool more = v.HasMore();
if (more)
{
libpl.PL_retry(v.Handle);
return res ? 3 : 0;
}
return res ? 1 : 0;
} break;
case FRG.PL_CUTTED:
{
var v = NondetContextHandle.FindHandle(control);
bool res = v.Close(new PlTermV(closureTerm, blockOn));
NondetContextHandle.ReleaseHandle(v);
return res ? 1 : 0;
} break;
default:
{
throw new PlException("no frg");
return libpl.PL_fail;
}
break;
}
}
}
public class ClosureDelegate : PrologGenericDelegate, IDisposable, SCCH
{
private object[] Result;
private readonly EventInfo Event;
private readonly object Instance;
private uint closureTerm;
///
/// ?- closure(v(V1,V2),format('I was called with ~q ~q ...',[V1,V2]).
///
///
///
///
public ClosureDelegate(EventInfo info, object instance, PlTerm clousreTerm)
{
Event = info;
Instance = instance;
SetInstanceOfDelegateType(info.EventHandlerType);
PlTerm plC = PlTerm.PlVar();
PrologCLR.PlCall("system", "copy_term", new PlTermV(clousreTerm, plC));
this.closureTerm = libpl.PL_record(clousreTerm.TermRef);
Event.AddEventHandler(instance, Delegate);
}
public override object CallProlog(params object[] paramz)
{
return CallPrologFast(paramz);
}
public override void CallPrologV(params object[] paramz)
{
CallPrologFast(paramz);
}
public override object CallPrologFast(object[] paramz)
{
PrologCLR.RegisterCurrentThread();
var results = paramz;
PlTerm plC = PlTerm.PlVar();
libpl.PL_recorded(closureTerm, plC.TermRef);
PlTerm ctestVars = plC.Arg(0);
PlTerm ctestCode = plC.Arg(1);
PlTerm[] terms = PrologCLR.ToTermArray(ctestVars);
int idx = terms.Length - 1;
int resdex = results.Length - 1;;
while (idx >= 0 && resdex >= 0)
{
terms[idx--].FromObject(results[resdex--]);
}
PrologCLR.PlCall("user", "call", new PlTermV(ctestCode, 1));
return null;
}
#region Implementation of IDisposable
///
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
///
/// 2
public void Dispose()
{
Event.RemoveEventHandler(Instance, Delegate);
if (closureTerm != 0) libpl.PL_erase(closureTerm);
closureTerm = 0;
}
#endregion
#region Implementation of SCCH
public bool Setup(PlTermV a0)
{
return true;
}
public bool Call(PlTermV a0)
{
// throw new NotImplementedException();
return true;
}
public bool Close(PlTermV a0)
{
//throw new NotImplementedException();
return true;
}
public bool HasMore()
{
// throw new NotImplementedException();
return false;
}
#endregion
}
///
/// Same as Queue except Dequeue function blocks until there is an object to return.
/// Note: This class does not need to be synchronized
///
public class BlockingQueue : Queue
{
private object SyncRoot;
private bool open = true;
///
/// Create new BlockingQueue.
///
/// The System.Collections.ICollection to copy elements from
public BlockingQueue(IEnumerable col)
: base(col)
{
SyncRoot = new object();
open = true;
}
///
/// Create new BlockingQueue.
///
/// The initial number of elements that the queue can contain
public BlockingQueue(int capacity)
: base(capacity)
{
SyncRoot = new object();
open = true;
}
///
/// Create new BlockingQueue.
///
public BlockingQueue()
: base()
{
SyncRoot = new object();
open = true;
}
///
/// BlockingQueue Destructor (Close queue, resume any waiting thread).
///
~BlockingQueue()
{
Close();
}
///
/// Remove all objects from the Queue.
///
public new void Clear()
{
lock (SyncRoot)
{
base.Clear();
}
}
///
/// Remove all objects from the Queue, resume all dequeue threads.
///
public void Close()
{
lock (SyncRoot)
{
open = false;
base.Clear();
Monitor.PulseAll(SyncRoot); // resume any waiting threads
}
}
///
/// Removes and returns the object at the beginning of the Queue.
///
/// Object in queue.
public new T Dequeue()
{
return Dequeue(Timeout.Infinite);
}
///
/// Removes and returns the object at the beginning of the Queue.
///
/// time to wait before returning
/// Object in queue.
public T Dequeue(TimeSpan timeout)
{
return Dequeue(timeout.Milliseconds);
}
///
/// Removes and returns the object at the beginning of the Queue.
///
/// time to wait before returning (in milliseconds)
/// Object in queue.
public T Dequeue(int timeout)
{
lock (SyncRoot)
{
while (open && (base.Count == 0))
{
if (!Monitor.Wait(SyncRoot, timeout))
throw new InvalidOperationException("Timeout");
}
if (open)
return base.Dequeue();
else
throw new InvalidOperationException("Queue Closed");
}
}
public bool Dequeue(int timeout, ref T obj)
{
lock (SyncRoot)
{
while (open && (base.Count == 0))
{
if (!Monitor.Wait(SyncRoot, timeout))
return false;
}
if (open)
{
obj = base.Dequeue();
return true;
}
else
{
obj = default(T);
return false;
}
}
}
///
/// Adds an object to the end of the Queue
///
/// Object to put in queue
public new void Enqueue(string name, T obj)
{
lock (SyncRoot)
{
base.Enqueue(obj);
Monitor.Pulse(SyncRoot);
}
}
///
/// Open Queue.
///
public void Open()
{
lock (SyncRoot)
{
open = true;
}
}
///
/// Gets flag indicating if queue has been closed.
///
public bool Closed
{
get { return !open; }
}
}
public interface PrologKey
{
string Name { get; }
string Module { get; }
int Arity { get; }
}
public abstract class PrologGenericDelegate
{
public static BlockingQueue PrologEventQueue = new BlockingQueue();
static private void PrologEventLoop()
{
Action outgoingPacket = null;
// FIXME: This is kind of ridiculous. Port the HTB code from Simian over ASAP!
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
while (true)
{
if (PrologEventQueue.Dequeue(100, ref outgoingPacket))
{
// Very primitive rate limiting, keeps a fixed buffer of time between each packet
stopwatch.Stop();
if (stopwatch.ElapsedMilliseconds < 10)
{
//Logger.DebugLog(String.Format("Rate limiting, last packet was {0}ms ago", ms));
Thread.Sleep(10 - (int)stopwatch.ElapsedMilliseconds);
}
try
{
outgoingPacket();
}
catch (Exception)
{
}
stopwatch.Start();
}
}
}
public static Thread PrologGenericDelegateThread;
static public void EnsureStated()
{
if (PrologGenericDelegateThread == null)
{
PrologGenericDelegateThread = new Thread(PrologEventLoop);
PrologGenericDelegateThread.Name = "PrologEventSerializer";
PrologGenericDelegateThread.TrySetApartmentState(ApartmentState.STA);
PrologGenericDelegateThread.IsBackground = true;
PrologCLR.RegisterThread(PrologGenericDelegateThread);
PrologGenericDelegateThread.Start();
}
}
#if USE_MUSHDLR
public static TaskQueueHandler PrologEventQueue = new TaskQueueHandler("PrologEventQueue");
#endif
private Type[] ParamTypes;
private bool IsVoid;
private int ParamArity = -1;
///
/// prolog predicate arity (invokeMethod.IsStatic ? 0 : 1) + ParamArity + (IsVoid ? 0 : 1);
///
public int PrologArity;
public Delegate Delegate;
public Type ReturnType;
private MethodInfo _handlerMethod;
public MethodInfo HandlerMethod
{
get
{
if (_handlerMethod != null) return _handlerMethod;
if (ParamTypes == null) throw new InvalidOperationException("First set instance of DelegateType!");
Type c = GetType();
if (IsVoid)
{
if (ParamArity == 0) return c.GetMethod("GenericFun0");
return c.GetMethod("GenericFun" + ParamArity).MakeGenericMethod(ParamTypes);
}
Type[] typesPlusReturn = new Type[ParamArity + 1];
Array.Copy(ParamTypes, typesPlusReturn, ParamArity);
typesPlusReturn[ParamArity] = ReturnType;
return _handlerMethod = c.GetMethod("GenericFunR" + ParamArity).MakeGenericMethod(typesPlusReturn);
}
}
public void SetInstanceOfDelegateType(Type delegateType)
{
var invokeMethod = delegateType.GetMethod("Invoke");
ReturnType = invokeMethod.ReturnType;
ParameterInfo[] parms = invokeMethod.GetParameters();
IsVoid = ReturnType == typeof(void);
ParamArity = parms.Length;
// For non static we like to send the first argument in from the Origin's value
PrologArity = (invokeMethod.IsStatic ? 0 : 1) + ParamArity + (IsVoid ? 0 : 1);
ParamTypes = new Type[ParamArity];
for (int i = 0; i < ParamArity; i++)
{
ParamTypes[i] = parms[i].ParameterType;
}
Delegate = Delegate.CreateDelegate(delegateType, this, HandlerMethod);
//SyncLock = SyncLock ?? Delegate;
}
//public abstract object CallProlog(params object[] args);
// non-void functions 0-6
public R GenericFunR0()
{
return (R)CallProlog();
}
public R GenericFunR1(A a)
{
return (R)CallProlog(a);
}
public R GenericFunR2(A a, B b)
{
return (R)CallProlog(a, b);
}
public R GenericFunR3(A a, B b, C c)
{
return (R)CallProlog(a, b, c);
}
public R GenericFunR4(A a, B b, C c, D d)
{
return (R)CallProlog(a, b, c, d);
}
public R GenericFunR5(A a, B b, C c, D d, E e)
{
return (R)CallProlog(a, b, c, d, e);
}
public R GenericFunR6(A a, B b, C c, D d, E e, F f)
{
return (R)CallProlog(a, b, c, d, e, f);
}
public R GenericFunR7(A a, B b, C c, D d, E e, F f, G g)
{
return (R)CallProlog(a, b, c, d, e, f, g);
}
public R GenericFunR8(A a, B b, C c, D d, E e, F f, G g, H h)
{
return (R)CallProlog(a, b, c, d, e, f, g, h);
}
// void functions 0-6
public void GenericFun0()
{
CallPrologV();
}
public void GenericFun1(A a)
{
CallPrologV(a);
}
public void GenericFun2(A a, B b)
{
CallPrologV(a, b);
}
public void GenericFun3(A a, B b, C c)
{
CallPrologV(a, b, c);
}
public void GenericFun4(A a, B b, C c, D d)
{
CallPrologV(a, b, c, d);
}
public void GenericFun5(A a, B b, C c, D d, E e)
{
CallPrologV(a, b, c, d, e);
}
public void GenericFun6(A a, B b, C c, D d, E e, F f)
{
CallPrologV(a, b, c, d, e, f);
}
public void GenericFun7(A a, B b, C c, D d, E e, F f, G g)
{
CallPrologV(a, b, c, d, e, f, g);
}
public void GenericFun8(A a, B b, C c, D d, E e, F f, G g, H h)
{
CallPrologV(a, b, c, d, e, f, g, h);
}
public virtual void CallPrologV(params object[] paramz)
{
if (!IsUsingGlobalQueue)
{
CallProlog0(paramz);
return;
}
PrologEventQueue.Open();
string threadName = null;// "CallProlog " + Thread.CurrentThread.Name;
PrologEventQueue.Enqueue(threadName, () => CallProlog0(paramz));
EnsureStated();
}
public virtual object CallProlog(params object[] paramz)
{
if (!IsUsingGlobalQueue) return CallProlog0(paramz);
string threadName = null;//"CallProlog " + Thread.CurrentThread.Name;
AutoResetEvent are = new AutoResetEvent(false);
object[] result = new object[1];
PrologEventQueue.Enqueue(threadName, () =>
{
result[0] = CallProlog0(paramz);
are.Set();
});
EnsureStated();
are.WaitOne();
return result[0];
}
private object CallProlog0(object[] paramz)
{
if (IsSyncronous)
{
var syncLock = SyncLock ?? Delegate;
if (syncLock != null)
{
lock (syncLock)
{
return CallPrologFast(paramz);
}
}
}
return CallPrologFast(paramz);
}
public abstract object CallPrologFast(object[] paramz);
static public bool IsUsingGlobalQueue = true;
public bool IsSyncronous = true;
public object SyncLock;
}
}