<commit_before>/* GStreamer
 * Copyright (C) 1999,2000 <NAME> <<EMAIL>>
 *                    2000 <NAME> <<EMAIL>>
 *
 * gstscheduler.c: Default scheduling code for most cases
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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.
 */

/*#define GST_DEBUG_ENABLED */
#include "../gst.h"

#include "cothreads_compat.h"

typedef struct _GstSchedulerChain GstSchedulerChain;

#define GST_PAD_THREADSTATE(pad)	(cothread*) (GST_PAD_CAST (pad)->sched_private)
#define GST_ELEMENT_THREADSTATE(elem)	(cothread*) (GST_ELEMENT_CAST (elem)->sched_private)

#define GST_ELEMENT_COTHREAD_STOPPING			GST_ELEMENT_SCHEDULER_PRIVATE1
#define GST_ELEMENT_IS_COTHREAD_STOPPING(element)	GST_FLAG_IS_SET((element), GST_ELEMENT_COTHREAD_STOPPING)

typedef struct _GstBasicScheduler GstBasicScheduler;
typedef struct _GstBasicSchedulerClass GstBasicSchedulerClass;

struct _GstSchedulerChain {
  GstBasicScheduler *sched;

  GList *disabled;

  GList *elements;
  gint num_elements;

  GstElement *entry;

  gint cothreaded_elements;
  gboolean schedule;
};

#define GST_TYPE_BASIC_SCHEDULER \
  (gst_basic_scheduler_get_type())
#define GST_BASIC_SCHEDULER(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BASIC_SCHEDULER,GstBasicScheduler))
#define GST_BASIC_SCHEDULER_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BASIC_SCHEDULER,GstBasicSchedulerClass))
#define GST_IS_BASIC_SCHEDULER(obj) \
  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BASIC_SCHEDULER))
#define GST_IS_BASIC_SCHEDULER_CLASS(obj) \
  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASIC_SCHEDULER))

#define GST_BASIC_SCHEDULER_CAST(sched)	((GstBasicScheduler *)(sched))
#define SCHED(element) GST_BASIC_SCHEDULER_CAST (GST_ELEMENT_SCHED (element))

typedef enum {
  GST_BASIC_SCHEDULER_STATE_NONE,
  GST_BASIC_SCHEDULER_STATE_STOPPED,
  GST_BASIC_SCHEDULER_STATE_ERROR,
  GST_BASIC_SCHEDULER_STATE_RUNNING,
} GstBasicSchedulerState;

typedef enum {
  /* something important has changed inside the scheduler */
  GST_BASIC_SCHEDULER_CHANGE	= GST_SCHEDULER_FLAG_LAST,
} GstBasicSchedulerFlags;

struct _GstBasicScheduler {
  GstScheduler parent;

  GList *elements;
  gint num_elements;

  GList *chains;
  gint num_chains;

  GstBasicSchedulerState state;
  
  cothread_context *context;
  GstElement *current;
};

struct _GstBasicSchedulerClass {
  GstSchedulerClass parent_class;
};

static GType _gst_basic_scheduler_type = 0;

static void 		gst_basic_scheduler_class_init 		(GstBasicSchedulerClass * klass);
static void 		gst_basic_scheduler_init 		(GstBasicScheduler * scheduler);

static void 		gst_basic_scheduler_dispose 		(GObject *object);

static void 		gst_basic_scheduler_setup 		(GstScheduler *sched);
static void 		gst_basic_scheduler_reset 		(GstScheduler *sched);
static void		gst_basic_scheduler_add_element		(GstScheduler *sched, GstElement *element);
static void     	gst_basic_scheduler_remove_element	(GstScheduler *sched, GstElement *element);
static GstElementStateReturn  
			gst_basic_scheduler_state_transition	(GstScheduler *sched, GstElement *element, gint transition);
static void 		gst_basic_scheduler_lock_element 	(GstScheduler *sched, GstElement *element);
static void 		gst_basic_scheduler_unlock_element 	(GstScheduler *sched, GstElement *element);
static void 		gst_basic_scheduler_yield 		(GstScheduler *sched, GstElement *element);
static gboolean		gst_basic_scheduler_interrupt 		(GstScheduler *sched, GstElement *element);
static void 		gst_basic_scheduler_error	 	(GstScheduler *sched, GstElement *element);
static void     	gst_basic_scheduler_pad_connect		(GstScheduler *sched, GstPad *srcpad, GstPad *sinkpad);
static void     	gst_basic_scheduler_pad_disconnect 	(GstScheduler *sched, GstPad *srcpad, GstPad *sinkpad);
static GstPad*  	gst_basic_scheduler_pad_select 		(GstScheduler *sched, GList *padlist);
static GstClockReturn	gst_basic_scheduler_clock_wait	 	(GstScheduler *sched, GstElement *element,
								 GstClock *clock, GstClockTime time, GstClockTimeDiff *jitter);
static GstSchedulerState
			gst_basic_scheduler_iterate    		(GstScheduler *sched);

static void     	gst_basic_scheduler_show  		(GstScheduler *sched);

static GstSchedulerClass *parent_class = NULL;

/* for threaded bins, these pre- and post-run functions lock and unlock the
 * elements. we have to avoid deadlocks, so we make these convenience macros
 * that will avoid using do_cothread_switch from within the scheduler. */

#define do_element_switch(element) G_STMT_START{		\
  GstElement *from = SCHED (element)->current;			\
  if (from && from->post_run_func)				\
    from->post_run_func (from);					\
  SCHED (element)->current = element;				\
  if (element->pre_run_func)					\
    element->pre_run_func (element);				\
  do_cothread_switch (GST_ELEMENT_THREADSTATE (element));	\
}G_STMT_END

#define do_switch_to_main(sched) G_STMT_START{			\
  GstElement *current = ((GstBasicScheduler*)sched)->current;	\
  if (current && current->post_run_func)			\
    current->post_run_func (current);				\
  SCHED (current)->current = NULL;				\
  do_cothread_switch						\
    (do_cothread_get_main					\
       (((GstBasicScheduler*)sched)->context));			\
}G_STMT_END

#define do_switch_from_main(entry) G_STMT_START{		\
  if (entry->pre_run_func)					\
    entry->pre_run_func (entry);				\
  SCHED (entry)->current = entry;				\
  do_cothread_switch (GST_ELEMENT_THREADSTATE (entry));		\
}G_STMT_END

static GType
gst_basic_scheduler_get_type (void)
{
  if (!_gst_basic_scheduler_type) {
    static const GTypeInfo scheduler_info = {
      sizeof (GstBasicSchedulerClass),
      NULL,
      NULL,
      (GClassInitFunc) gst_basic_scheduler_class_init,
      NULL,
      NULL,
      sizeof (GstBasicScheduler),
      0,
      (GInstanceInitFunc) gst_basic_scheduler_init,
      NULL
    };

    _gst_basic_scheduler_type = g_type_register_static (GST_TYPE_SCHEDULER, "Gst"COTHREADS_NAME_CAPITAL"Scheduler", &scheduler_info, 0);
  }
  return _gst_basic_scheduler_type;
}

static void
gst_basic_scheduler_class_init (GstBasicSchedulerClass * klass)
{
  GObjectClass *gobject_class;
  GstObjectClass *gstobject_class;
  GstSchedulerClass *gstscheduler_class;

  gobject_class = (GObjectClass*)klass;
  gstobject_class = (GstObjectClass*)klass;
  gstscheduler_class = (GstSchedulerClass*)klass;

  parent_class = g_type_class_ref (GST_TYPE_SCHEDULER);

  gobject_class->dispose	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_dispose);

  gstscheduler_class->setup 		= GST_DEBUG_FUNCPTR (gst_basic_scheduler_setup);
  gstscheduler_class->reset	 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_reset);
  gstscheduler_class->add_element 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_add_element);
  gstscheduler_class->remove_element 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_remove_element);
  gstscheduler_class->state_transition 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_state_transition);
  gstscheduler_class->lock_element 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_lock_element);
  gstscheduler_class->unlock_element 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_unlock_element);
  gstscheduler_class->yield	 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_yield);
  gstscheduler_class->interrupt 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_interrupt);
  gstscheduler_class->error	 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_error);
  gstscheduler_class->pad_connect 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_pad_connect);
  gstscheduler_class->pad_disconnect 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_pad_disconnect);
  gstscheduler_class->pad_select	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_pad_select);
  gstscheduler_class->clock_wait	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_clock_wait);
  gstscheduler_class->iterate 		= GST_DEBUG_FUNCPTR (gst_basic_scheduler_iterate);

  gstscheduler_class->show 		= GST_DEBUG_FUNCPTR (gst_basic_scheduler_show);
  
  do_cothreads_init(NULL);
}

static void
gst_basic_scheduler_init (GstBasicScheduler *scheduler)
{
  scheduler->elements = NULL;
  scheduler->num_elements = 0;
  scheduler->chains = NULL;
  scheduler->num_chains = 0;
}

static void
gst_basic_scheduler_dispose (GObject *object)
{
  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static gboolean
plugin_init (GModule *module, GstPlugin *plugin)
{
  GstSchedulerFactory *factory;

  gst_plugin_set_longname (plugin, "A basic scheduler");

  factory = gst_scheduler_factory_new (COTHREADS_NAME,
	                              "A basic scheduler, it uses "COTHREADS_NAME" cothreads",
		                      gst_basic_scheduler_get_type());

  if (factory != NULL) {
    gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
  }
  else {
    g_warning ("could not register scheduler: "COTHREADS_NAME);
  }
  return TRUE;
}

GstPluginDesc plugin_desc = {
  GST_VERSION_MAJOR,
  GST_VERSION_MINOR,
  "gst"COTHREADS_NAME"scheduler",
  plugin_init
};

static int
gst_basic_scheduler_loopfunc_wrapper (int argc, char *argv[])
{
  GstElement *element = GST_ELEMENT_CAST (argv);
  G_GNUC_UNUSED const gchar *name = GST_ELEMENT_NAME (element);

  GST_DEBUG_ENTER ("(%d,'%s')", argc, name);

  do {
    GST_DEBUG (GST_CAT_DATAFLOW, "calling loopfunc %s for element %s",
	       GST_DEBUG_FUNCPTR_NAME (element->loopfunc), name);
    (element->loopfunc) (element);
    GST_DEBUG (GST_CAT_DATAFLOW, "element %s ended loop function", name);

  } while (!GST_ELEMENT_IS_COTHREAD_STOPPING (element));
  GST_FLAG_UNSET (element, GST_ELEMENT_COTHREAD_STOPPING);

  GST_DEBUG_LEAVE ("(%d,'%s')", argc, name);
  return 0;
}

static int
gst_basic_scheduler_chain_wrapper (int argc, char *argv[])
{
  GstElement *element = GST_ELEMENT_CAST (argv);
  G_GNUC_UNUSED const gchar *name = GST_ELEMENT_NAME (element);

  GST_DEBUG_ENTER ("(\"%s\")", name);

  GST_DEBUG (GST_CAT_DATAFLOW, "stepping through pads");

  gst_object_ref (GST_OBJECT (element));
  do {
    GList *pads = element->pads;

    while (pads) {
      GstPad *pad = GST_PAD (pads->data);
      GstRealPad *realpad;

      pads = g_list_next (pads);
      if (!GST_IS_REAL_PAD (pad))
	continue;

      realpad = GST_REAL_PAD_CAST (pad);

      if (GST_RPAD_DIRECTION (realpad) == GST_PAD_SINK) {
	GstBuffer *buf;

	GST_DEBUG (GST_CAT_DATAFLOW, "pulling data from %s:%s", name, GST_PAD_NAME (pad));
	buf = gst_pad_pull (pad);
	if (buf) {
	  if (GST_IS_EVENT (buf) && !GST_ELEMENT_IS_EVENT_AWARE (element)) {
	    gst_pad_send_event (pad, GST_EVENT (buf));
	  }
	  else {
	    GST_DEBUG (GST_CAT_DATAFLOW, "calling chain function of %s:%s", name,
		       GST_PAD_NAME (pad));
	    GST_RPAD_CHAINFUNC (realpad) (pad, buf);
	    GST_DEBUG (GST_CAT_DATAFLOW, "calling chain function of element %s done", name);
	  }
	}
      }
    }
  } while (!GST_ELEMENT_IS_COTHREAD_STOPPING (element));
  GST_FLAG_UNSET (element, GST_ELEMENT_COTHREAD_STOPPING);

  /* due to oddities in the cothreads code, when this function returns it will
   * switch to the main cothread. thus, we need to unlock the current element. */
  if (SCHED (element)) {
    if (SCHED (element)->current && SCHED (element)->current->post_run_func) {
      SCHED (element)->current->post_run_func (SCHED (element)->current);
    }
    SCHED (element)->current = NULL;
  }

  GST_DEBUG_LEAVE ("(%d,'%s')", argc, name);
  gst_object_unref (GST_OBJECT (element));
  return 0;
}

static int
gst_basic_scheduler_src_wrapper (int argc, char *argv[])
{
  GstElement *element = GST_ELEMENT_CAST (argv);
  GList *pads;
  GstRealPad *realpad;
  GstBuffer *buf = NULL;
  G_GNUC_UNUSED const gchar *name = GST_ELEMENT_NAME (element);

  GST_DEBUG_ENTER ("(%d,\"%s\")", argc, name);

  do {
    pads = element->pads;
    while (pads) {

      if (!GST_IS_REAL_PAD (pads->data))
	continue;

      realpad = GST_REAL_PAD_CAST (pads->data);

      pads = g_list_next (pads);
      if (GST_RPAD_DIRECTION (realpad) == GST_PAD_SRC) {
	GST_DEBUG (GST_CAT_DATAFLOW, "calling _getfunc for %s:%s", GST_DEBUG_PAD_NAME (realpad));
	g_return_val_if_fail (GST_RPAD_GETFUNC (realpad) != NULL, 0);
	buf = GST_RPAD_GETFUNC (realpad) (GST_PAD_CAST (realpad));
	if (buf) {
	  GST_DEBUG (GST_CAT_DATAFLOW, "calling gst_pad_push on pad %s:%s",
		     GST_DEBUG_PAD_NAME (realpad));
	  gst_pad_push (GST_PAD_CAST (realpad), buf);
	}
      }
    }
  } while (!GST_ELEMENT_IS_COTHREAD_STOPPING (element));

  GST_FLAG_UNSET (element, GST_ELEMENT_COTHREAD_STOPPING);

  /* due to oddities in the cothreads code, when this function returns it will
   * switch to the main cothread. thus, we need to unlock the current element. */
  if (SCHED (element)->current->post_run_func)
    SCHED (element)->current->post_run_func (SCHED (element)->current);
  SCHED (element)->current = NULL;

  GST_DEBUG_LEAVE ("");
  return 0;
}

static void
gst_basic_scheduler_chainhandler_proxy (GstPad * pad, GstBuffer * buf)
{
  gint loop_count = 100;
  GstElement *parent;
  GstRealPad *peer;

  parent = GST_PAD_PARENT (pad);
  peer = GST_RPAD_PEER (pad);

  GST_DEBUG_ENTER ("(%s:%s)", GST_DEBUG_PAD_NAME (pad));
  GST_DEBUG (GST_CAT_DATAFLOW, "putting buffer %p in peer \"%s:%s\"'s pen", buf,
	     GST_DEBUG_PAD_NAME (peer));

  /* 
   * loop until the bufferpen is empty so we can fill it up again
   */
  while (GST_RPAD_BUFPEN (GST_RPAD_PEER (pad)) != NULL && --loop_count) {
    GST_DEBUG (GST_CAT_DATAFLOW, "switching to %p to empty bufpen %d",
	       GST_ELEMENT_THREADSTATE (parent), loop_count);

    do_element_switch (parent);

    /* we may no longer be the same pad, check. */
    if (GST_RPAD_PEER (peer) != (GstRealPad *) pad) {
      GST_DEBUG (GST_CAT_DATAFLOW, "new pad in mid-switch!");
      pad = (GstPad *) GST_RPAD_PEER (peer);
    }
    parent = GST_PAD_PARENT (pad);
    peer = GST_RPAD_PEER (pad);
  }

  if (loop_count == 0) {
    gst_element_error (parent, 
		    "(internal error) maximum number of switches exceeded");
    return;
  }

  g_assert (GST_RPAD_BUFPEN (GST_RPAD_PEER (pad)) == NULL);

  /* now fill the bufferpen and switch so it can be consumed */
  GST_RPAD_BUFPEN (GST_RPAD_PEER (pad)) = buf;
  GST_DEBUG (GST_CAT_DATAFLOW, "switching to %p",
	     GST_ELEMENT_THREADSTATE (GST_PAD_PARENT (pad)));

  do_element_switch (parent);

  GST_DEBUG (GST_CAT_DATAFLOW, "done switching");
}

static void
gst_basic_scheduler_select_proxy (GstPad * pad, GstBuffer * buf)
{
  GstElement *parent;
  
  parent = GST_PAD_PARENT (pad);

  GST_DEBUG_ENTER ("(%s:%s)", GST_DEBUG_PAD_NAME (pad));

  GST_DEBUG (GST_CAT_DATAFLOW, "putting buffer %p in peer's pen", buf);

  g_assert (GST_RPAD_BUFPEN (GST_RPAD_PEER (pad)) == NULL);
  /* now fill the bufferpen and switch so it can be consumed */
  GST_RPAD_BUFPEN (GST_RPAD_PEER (pad)) = buf;
  GST_DEBUG (GST_CAT_DATAFLOW, "switching to %p",
	     GST_ELEMENT_THREADSTATE (parent));
  parent->select_pad = pad;

  do_element_switch (parent);
  
  GST_DEBUG (GST_CAT_DATAFLOW, "done switching");
}


static GstBuffer *
gst_basic_scheduler_gethandler_proxy (GstPad * pad)
{
  GstBuffer *buf;
  GstElement *parent;
  GstRealPad *peer;

  GST_DEBUG_ENTER ("(%s:%s)", GST_DEBUG_PAD_NAME (pad));

  parent = GST_PAD_PARENT (pad);
  peer = GST_RPAD_PEER (pad);

  /* FIXME this should be bounded */
  /* we will loop switching to the peer until it's filled up the bufferpen */
  while (GST_RPAD_BUFPEN (pad) == NULL) {

    GST_DEBUG (GST_CAT_DATAFLOW, "switching to \"%s\": %p to fill bufpen",
	       GST_ELEMENT_NAME (parent),
	       GST_ELEMENT_THREADSTATE (parent));

    do_element_switch (parent);

    /* we may no longer be the same pad, check. */
    if (GST_RPAD_PEER (peer) != (GstRealPad *) pad) {
      GST_DEBUG (GST_CAT_DATAFLOW, "new pad in mid-switch!");
      pad = (GstPad *) GST_RPAD_PEER (peer);
      if (!pad) {
	gst_element_error (parent, "pad unconnected");
      }
      parent = GST_PAD_PARENT (pad);
      peer = GST_RPAD_PEER (pad);
    }
  }
  GST_DEBUG (GST_CAT_DATAFLOW, "done switching");

  /* now grab the buffer from the pen, clear the pen, and return the buffer */
  buf = GST_RPAD_BUFPEN (pad);
  GST_RPAD_BUFPEN (pad) = NULL;

  return buf;
}

static gboolean
gst_basic_scheduler_cothreaded_chain (GstBin * bin, GstSchedulerChain * chain)
{
  GList *elements;
  GstElement *element;
  cothread_func wrapper_function;
  GList *pads;
  GstPad *pad;

  GST_DEBUG (GST_CAT_SCHEDULING, "chain is using COTHREADS");

  g_assert (chain->sched->context != NULL);

  /* walk through all the chain's elements */
  elements = chain->elements;
  while (elements) {
    gboolean decoupled;

    element = GST_ELEMENT_CAST (elements->data);
    elements = g_list_next (elements);

    decoupled = (GST_FLAG_IS_SET (element, GST_ELEMENT_DECOUPLED) ? TRUE :  FALSE);

    /* start out without a wrapper function, we select it later */
    wrapper_function = NULL;

    /* if the element has a loopfunc... */
    if (element->loopfunc != NULL) {
      wrapper_function = GST_DEBUG_FUNCPTR (gst_basic_scheduler_loopfunc_wrapper);
      GST_DEBUG (GST_CAT_SCHEDULING, "element '%s' is a loop-based", GST_ELEMENT_NAME (element));
    }
    else {
      /* otherwise we need to decide what kind of cothread */
      /* if it's not DECOUPLED, we decide based on whether it's a source or not */
      if (!decoupled) {
	/* if it doesn't have any sinks, it must be a source (duh) */
	if (element->numsinkpads == 0) {
	  wrapper_function = GST_DEBUG_FUNCPTR (gst_basic_scheduler_src_wrapper);
	  GST_DEBUG (GST_CAT_SCHEDULING, "element '%s' is a source, using _src_wrapper",
		     GST_ELEMENT_NAME (element));
	}
	else {
	  wrapper_function = GST_DEBUG_FUNCPTR (gst_basic_scheduler_chain_wrapper);
	  GST_DEBUG (GST_CAT_SCHEDULING, "element '%s' is a filter, using _chain_wrapper",
		     GST_ELEMENT_NAME (element));
	}
      }
    }

    /* now we have to walk through the pads to set up their state */
    pads = gst_element_get_pad_list (element);
    while (pads) {
      GstPad *peerpad;

      pad = GST_PAD (pads->data);
      pads = g_list_next (pads);

      if (!GST_IS_REAL_PAD (pad))
	continue;
      
      peerpad = GST_PAD_PEER (pad);
      if (peerpad) {
	GstElement *peerelement = GST_ELEMENT_CAST (GST_PAD_PARENT (peerpad));
	gboolean different_sched = (peerelement->sched != GST_SCHEDULER (chain->sched));
	gboolean peer_decoupled = GST_FLAG_IS_SET (peerelement, GST_ELEMENT_DECOUPLED);

        GST_DEBUG (GST_CAT_SCHEDULING, "inspecting pad %s:%s", GST_DEBUG_PAD_NAME (peerpad));

	/* we don't need to check this for decoupled elements */
	if (!decoupled) {
	  /* if the peer element is in another schedule, it's not decoupled and we are not decoupled
	   * either, we have an error */
	  if (different_sched && !peer_decoupled) 
 	  {
            gst_element_error (element, "element \"%s\" is not decoupled but has pads in different schedulers",
			  GST_ELEMENT_NAME (element), NULL);
	    return FALSE;
	  }
	  /* ok, the peer is in a different scheduler and is decoupled, we need to set the
	   * handlers so we can talk with it */
	  else if (different_sched) {
	    if (GST_RPAD_DIRECTION (peerpad) == GST_PAD_SINK) {
	      GST_DEBUG (GST_CAT_SCHEDULING, "copying chain function into push proxy for peer %s:%s",
		     GST_DEBUG_PAD_NAME (peerpad));
	      GST_RPAD_CHAINHANDLER (peerpad) = GST_RPAD_CHAINFUNC (peerpad);
	    }
	    else {
	      GST_DEBUG (GST_CAT_SCHEDULING, "copying get function into pull proxy for peer %s:%s",
		     GST_DEBUG_PAD_NAME (peerpad));
	      GST_RPAD_GETHANDLER (peerpad) = GST_RPAD_GETFUNC (peerpad);
	    }
	  }
	}
      }

      /* if the element is DECOUPLED or outside the manager, we have to chain */
      if (decoupled) {
	/* set the chain proxies */
	if (GST_RPAD_DIRECTION (pad) == GST_PAD_SINK) {
	  GST_DEBUG (GST_CAT_SCHEDULING, "copying chain function into push proxy for %s:%s",
		     GST_DEBUG_PAD_NAME (pad));
	  GST_RPAD_CHAINHANDLER (pad) = GST_RPAD_CHAINFUNC (pad);
	}
	else {
	  GST_DEBUG (GST_CAT_SCHEDULING, "copying get function into pull proxy for %s:%s",
		     GST_DEBUG_PAD_NAME (pad));
	  GST_RPAD_GETHANDLER (pad) = GST_RPAD_GETFUNC (pad);
	}
      }
      /* otherwise we really are a cothread */
      else {
	if (GST_RPAD_DIRECTION (pad) == GST_PAD_SINK) {
	  GST_DEBUG (GST_CAT_SCHEDULING, "setting cothreaded push proxy for sinkpad %s:%s",
	     GST_DEBUG_PAD_NAME (pad));
	  GST_RPAD_CHAINHANDLER (pad) = GST_DEBUG_FUNCPTR (gst_basic_scheduler_chainhandler_proxy);
	}
	else {
	  GST_DEBUG (GST_CAT_SCHEDULING, "setting cothreaded pull proxy for srcpad %s:%s",
	     GST_DEBUG_PAD_NAME (pad));
	  GST_RPAD_GETHANDLER (pad) = GST_DEBUG_FUNCPTR (gst_basic_scheduler_gethandler_proxy);
	}
      }
    }

    /* need to set up the cothread now */
    if (wrapper_function != NULL) {
      if (GST_ELEMENT_THREADSTATE (element) == NULL) {
	do_cothread_create (GST_ELEMENT_THREADSTATE (element), chain->sched->context, 
			    wrapper_function, 0, (char **) element);
	if (GST_ELEMENT_THREADSTATE (element) == NULL) {
          gst_element_error (element, "could not create cothread for \"%s\"", 
			  GST_ELEMENT_NAME (element), NULL);
	  return FALSE;
	}
	GST_DEBUG (GST_CAT_SCHEDULING, "created cothread %p for '%s'", 
		   GST_ELEMENT_THREADSTATE (element),
		   GST_ELEMENT_NAME (element));
      } else {
	do_cothread_reset (GST_ELEMENT_THREADSTATE (element), chain->sched->context, 
			   wrapper_function, 0, (char **) element);
	GST_DEBUG (GST_CAT_SCHEDULING, "set wrapper function for '%s' to &%s",
		   GST_ELEMENT_NAME (element), GST_DEBUG_FUNCPTR_NAME (wrapper_function));
      }
    }
  }

  return TRUE;
}

static GstSchedulerChain *
gst_basic_scheduler_chain_new (GstBasicScheduler * sched)
{
  GstSchedulerChain *chain = g_new (GstSchedulerChain, 1);

  /* initialize the chain with sane values */
  chain->sched = sched;
  chain->disabled = NULL;
  chain->elements = NULL;
  chain->num_elements = 0;
  chain->entry = NULL;
  chain->cothreaded_elements = 0;
  chain->schedule = FALSE;

  /* add the chain to the schedulers' list of chains */
  sched->chains = g_list_prepend (sched->chains, chain);
  sched->num_chains++;

  /* notify the scheduler that something changed */
  GST_FLAG_SET(sched, GST_BASIC_SCHEDULER_CHANGE);

  GST_INFO (GST_CAT_SCHEDULING, "created new chain %p, now are %d chains in sched %p",
	    chain, sched->num_chains, sched);

  return chain;
}

static void
gst_basic_scheduler_chain_destroy (GstSchedulerChain * chain)
{
  GstBasicScheduler *sched = chain->sched;

  /* remove the chain from the schedulers' list of chains */
  sched->chains = g_list_remove (sched->chains, chain);
  sched->num_chains--;

  /* destroy the chain */
  g_list_free (chain->disabled);	/* should be empty... */
  g_list_free (chain->elements);	/* ditto 	      */

  GST_INFO (GST_CAT_SCHEDULING, "destroyed chain %p, now are %d chains in sched %p", chain,
	    sched->num_chains, sched);

  g_free (chain);

  /* notify the scheduler that something changed */
  GST_FLAG_SET(sched, GST_BASIC_SCHEDULER_CHANGE);
}

static void
gst_basic_scheduler_chain_add_element (GstSchedulerChain * chain, GstElement * element)
{
  GST_INFO (GST_CAT_SCHEDULING, "adding element \"%s\" to chain %p", GST_ELEMENT_NAME (element),
	    chain);

  /* set the sched pointer for the element */
  element->sched = GST_SCHEDULER (chain->sched);

  /* add the element to the list of 'disabled' elements */
  chain->disabled = g_list_prepend (chain->disabled, element);
  chain->num_elements++;

  /* notify the scheduler that something changed */
  GST_FLAG_SET(chain->sched, GST_BASIC_SCHEDULER_CHANGE);
}

static gboolean
gst_basic_scheduler_chain_enable_element (GstSchedulerChain * chain, GstElement * element)
{
  GST_INFO (GST_CAT_SCHEDULING, "enabling element \"%s\" in chain %p", GST_ELEMENT_NAME (element),
	    chain);

  /* remove from disabled list */
  chain->disabled = g_list_remove (chain->disabled, element);

  /* add to elements list */
  chain->elements = g_list_prepend (chain->elements, element);

  /* notify the scheduler that something changed */
  GST_FLAG_SET(chain->sched, GST_BASIC_SCHEDULER_CHANGE);
  /* GST_FLAG_UNSET(element, GST_ELEMENT_COTHREAD_STOPPING); */

  /* reschedule the chain */
  return gst_basic_scheduler_cothreaded_chain (GST_BIN (GST_SCHEDULER (chain->sched)->parent), chain);
}

static void
gst_basic_scheduler_chain_disable_element (GstSchedulerChain * chain, GstElement * element)
{
  GST_INFO (GST_CAT_SCHEDULING, "disabling element \"%s\" in chain %p", GST_ELEMENT_NAME (element),
	    chain);

  /* remove from elements list */
  chain->elements = g_list_remove (chain->elements, element);

  /* add to disabled list */
  chain->disabled = g_list_prepend (chain->disabled, element);

  /* notify the scheduler that something changed */
  GST_FLAG_SET(chain->sched, GST_BASIC_SCHEDULER_CHANGE);
  GST_FLAG_SET(element, GST_ELEMENT_COTHREAD_STOPPING);

  /* reschedule the chain */
/* FIXME this should be done only if manager state != NULL */
/*  gst_basic_scheduler_cothreaded_chain(GST_BIN(chain->sched->parent),chain); */
}

static void
gst_basic_scheduler_chain_remove_element (GstSchedulerChain * chain, GstElement * element)
{
  GST_INFO (GST_CAT_SCHEDULING, "removing element \"%s\" from chain %p", GST_ELEMENT_NAME (element),
	    chain);

  /* if it's active, deactivate it */
  if (g_list_find (chain->elements, element)) {
    gst_basic_scheduler_chain_disable_element (chain, element);
  }
  /* we have to check for a threadstate here because a queue doesn't have one */
  if (GST_ELEMENT_THREADSTATE (element)) {
    do_cothread_destroy (GST_ELEMENT_THREADSTATE (element));
    GST_ELEMENT_THREADSTATE (element) = NULL;
  }

  /* remove the element from the list of elements */
  chain->disabled = g_list_remove (chain->disabled, element);
  chain->num_elements--;

  /* notify the scheduler that something changed */
  GST_FLAG_SET(chain->sched, GST_BASIC_SCHEDULER_CHANGE);

  /* if there are no more elements in the chain, destroy the chain */
  if (chain->num_elements == 0)
    gst_basic_scheduler_chain_destroy (chain);

}

static void
gst_basic_scheduler_chain_elements (GstBasicScheduler * sched, GstElement * element1, GstElement * element2)
{
  GList *chains;
  GstSchedulerChain *chain;
  GstSchedulerChain *chain1 = NULL, *chain2 = NULL;
  GstElement *element;

  /* first find the chains that hold the two  */
  chains = sched->chains;
  while (chains) {
    chain = (GstSchedulerChain *) (chains->data);
    chains = g_list_next (chains);

    if (g_list_find (chain->disabled, element1))
      chain1 = chain;
    else if (g_list_find (chain->elements, element1))
      chain1 = chain;

    if (g_list_find (chain->disabled, element2))
      chain2 = chain;
    else if (g_list_find (chain->elements, element2))
      chain2 = chain;
  }

  /* first check to see if they're in the same chain, we're done if that's the case */
  if ((chain1 != NULL) && (chain1 == chain2)) {
    GST_INFO (GST_CAT_SCHEDULING, "elements are already in the same chain");
    return;
  }

  /* now, if neither element has a chain, create one */
  if ((chain1 == NULL) && (chain2 == NULL)) {
    GST_INFO (GST_CAT_SCHEDULING, "creating new chain to hold two new elements");
    chain = gst_basic_scheduler_chain_new (sched);
    gst_basic_scheduler_chain_add_element (chain, element1);
    gst_basic_scheduler_chain_add_element (chain, element2);
    /* FIXME chain changed here */
/*    gst_basic_scheduler_cothreaded_chain(chain->sched->parent,chain); */

    /* otherwise if both have chains already, join them */
  }
  else if ((chain1 != NULL) && (chain2 != NULL)) {
    GST_INFO (GST_CAT_SCHEDULING, "merging chain %p into chain %p", chain2, chain1);
    /* take the contents of chain2 and merge them into chain1 */
    chain1->disabled = g_list_concat (chain1->disabled, g_list_copy (chain2->disabled));
    chain1->elements = g_list_concat (chain1->elements, g_list_copy (chain2->elements));
    chain1->num_elements += chain2->num_elements;
    /* FIXME chain changed here */
/*    gst_basic_scheduler_cothreaded_chain(chain->sched->parent,chain); */

    gst_basic_scheduler_chain_destroy (chain2);

    /* otherwise one has a chain already, the other doesn't */
  }
  else {
    /* pick out which one has the chain, and which doesn't */
    if (chain1 != NULL)
      chain = chain1, element = element2;
    else
      chain = chain2, element = element1;

    GST_INFO (GST_CAT_SCHEDULING, "adding element to existing chain");
    gst_basic_scheduler_chain_add_element (chain, element);
    /* FIXME chain changed here */
/*    gst_basic_scheduler_cothreaded_chain(chain->sched->parent,chain); */
  }

}


/* find the chain within the scheduler that holds the element, if any */
static GstSchedulerChain *
gst_basic_scheduler_find_chain (GstBasicScheduler * sched, GstElement * element)
{
  GList *chains;
  GstSchedulerChain *chain;

  GST_INFO (GST_CAT_SCHEDULING, "searching for element \"%s\" in chains",
	    GST_ELEMENT_NAME (element));

  chains = sched->chains;
  while (chains) {
    chain = (GstSchedulerChain *) (chains->data);
    chains = g_list_next (chains);

    if (g_list_find (chain->elements, element))
      return chain;
    if (g_list_find (chain->disabled, element))
      return chain;
  }

  return NULL;
}

static void
gst_basic_scheduler_chain_recursive_add (GstSchedulerChain * chain, GstElement * element)
{
  GList *pads;
  GstPad *pad;
  GstElement *peerelement;

  /* add the element to the chain */
  gst_basic_scheduler_chain_add_element (chain, element);

  GST_DEBUG (GST_CAT_SCHEDULING, "recursing on element \"%s\"", GST_ELEMENT_NAME (element));
  /* now go through all the pads and see which peers can be added */
  pads = element->pads;
  while (pads) {
    pad = GST_PAD (pads->data);
    pads = g_list_next (pads);

    GST_DEBUG (GST_CAT_SCHEDULING, "have pad %s:%s, checking for valid peer",
	       GST_DEBUG_PAD_NAME (pad));
    /* if the peer exists and could be in the same chain */
    if (GST_PAD_PEER (pad)) {
      GST_DEBUG (GST_CAT_SCHEDULING, "has peer %s:%s", GST_DEBUG_PAD_NAME (GST_PAD_PEER (pad)));
      peerelement = GST_PAD_PARENT (GST_PAD_PEER (pad));
      if (GST_ELEMENT_SCHED (GST_PAD_PARENT (pad)) == GST_ELEMENT_SCHED (peerelement)) {
	GST_DEBUG (GST_CAT_SCHEDULING, "peer \"%s\" is valid for same chain",
		   GST_ELEMENT_NAME (peerelement));
	/* if it's not already in a chain, add it to this one */
	if (gst_basic_scheduler_find_chain (chain->sched, peerelement) == NULL) {
	  gst_basic_scheduler_chain_recursive_add (chain, peerelement);
	}
      }
    }
  }
}

/*
 * Entry points for this scheduler.
 */
static void
gst_basic_scheduler_setup (GstScheduler *sched)
{

  /* first create thread context */
  if (GST_BASIC_SCHEDULER_CAST (sched)->context == NULL) {
    GST_DEBUG (GST_CAT_SCHEDULING, "initializing cothread context");
    GST_BASIC_SCHEDULER_CAST (sched)->context = do_cothread_context_init ();
  }
}

static void
gst_basic_scheduler_reset (GstScheduler *sched)
{
  cothread_context *ctx;
  GList *elements = GST_BASIC_SCHEDULER_CAST (sched)->elements;

  while (elements) {
    /* FIXME: wingo, do we need to destroy the cothreads here? */
    GST_ELEMENT_THREADSTATE (elements->data) = NULL;
    elements = g_list_next (elements);
  }
  
  ctx = GST_BASIC_SCHEDULER_CAST (sched)->context;

  do_cothread_context_destroy (ctx);
  
  GST_BASIC_SCHEDULER_CAST (sched)->context = NULL;
}

static void
gst_basic_scheduler_add_element (GstScheduler * sched, GstElement * element)
{
  GList *pads;
  GstPad *pad;
  GstElement *peerelement;
  GstSchedulerChain *chain;
  GstBasicScheduler *bsched = GST_BASIC_SCHEDULER (sched);

  /* if it's already in this scheduler, don't bother doing anything */
  if (GST_ELEMENT_SCHED (element) == sched)
    return;

  GST_INFO (GST_CAT_SCHEDULING, "adding element \"%s\" to scheduler", GST_ELEMENT_NAME (element));

  /* if the element already has a scheduler something went wrong */
  if (GST_ELEMENT_SCHED (element)) {
    GST_ERROR (element, "grave error");
    return;
  }
  
  /* set the sched pointer in the element itself */
  GST_ELEMENT_SCHED (element) = sched;

  /* only deal with elements after this point, not bins */
  /* exception is made for Bin's that are schedulable, like the autoplugger */
  if (GST_IS_BIN (element) && !GST_FLAG_IS_SET (element, GST_BIN_SELF_SCHEDULABLE))
    return;

  /* first add it to the list of elements that are to be scheduled */
  bsched->elements = g_list_prepend (bsched->elements, element);
  bsched->num_elements++;

  /* create a chain to hold it, and add */
  chain = gst_basic_scheduler_chain_new (bsched);
  gst_basic_scheduler_chain_add_element (chain, element);

  /* set the sched pointer in all the pads */
  pads = element->pads;
  while (pads) {
    pad = GST_PAD (pads->data);
    pads = g_list_next (pads);

    /* we only operate on real pads */
    if (!GST_IS_REAL_PAD (pad))
      continue;

    /* set the pad's sched pointer */
    gst_pad_set_scheduler (pad, sched);

    /* if the peer element exists and is a candidate */
    if (GST_PAD_PEER (pad)) {
      peerelement = GST_PAD_PARENT (GST_PAD_PEER (pad));
      if (GST_ELEMENT_SCHED (element) == GST_ELEMENT_SCHED (peerelement)) {
	GST_INFO (GST_CAT_SCHEDULING, "peer is in same scheduler, chaining together");
	/* make sure that the two elements are in the same chain */
	gst_basic_scheduler_chain_elements (bsched, element, peerelement);
      }
    }
  }
}

static void
gst_basic_scheduler_remove_element (GstScheduler * sched, GstElement * element)
{
  GstSchedulerChain *chain;
  GstBasicScheduler *bsched = GST_BASIC_SCHEDULER (sched);

  if (g_list_find (bsched->elements, element)) {
    GST_INFO (GST_CAT_SCHEDULING, "removing element \"%s\" from scheduler",
	      GST_ELEMENT_NAME (element));

    /* if we are removing the currently scheduled element */
    if (bsched->current == element) {
       GST_FLAG_SET(element, GST_ELEMENT_COTHREAD_STOPPING);
       if (element->post_run_func)
         element->post_run_func (element);
       bsched->current = NULL;
    }
    /* find what chain the element is in */
    chain = gst_basic_scheduler_find_chain (bsched, element);

    /* remove it from its chain */
    gst_basic_scheduler_chain_remove_element (chain, element);

    /* remove it from the list of elements */
    bsched->elements = g_list_remove (bsched->elements, element);
    bsched->num_elements--;

    /* unset the scheduler pointer in the element */
    GST_ELEMENT_SCHED (element) = NULL;
  }
}

static GstElementStateReturn
gst_basic_scheduler_state_transition (GstScheduler *sched, GstElement *element, gint transition)
{
  GstSchedulerChain *chain;
  GstBasicScheduler *bsched = GST_BASIC_SCHEDULER (sched);

  /* check if our parent changed state */
  if (GST_SCHEDULER_PARENT (sched) == element) {
    GST_INFO (GST_CAT_SCHEDULING, "parent \"%s\" changed state", GST_ELEMENT_NAME (element));
    if (transition == GST_STATE_PLAYING_TO_PAUSED) {
      GST_INFO (GST_CAT_SCHEDULING, "setting scheduler state to stopped");
      GST_SCHEDULER_STATE (sched) = GST_SCHEDULER_STATE_STOPPED;
    }
    else if (transition == GST_STATE_PAUSED_TO_PLAYING) {
      GST_INFO (GST_CAT_SCHEDULING, "setting scheduler state to running");
      GST_SCHEDULER_STATE (sched) = GST_SCHEDULER_STATE_RUNNING;
    }
    else {
      GST_INFO (GST_CAT_SCHEDULING, "no interesting state change, doing nothing");
    }
  }
  else if (transition == GST_STATE_PLAYING_TO_PAUSED ||
           transition == GST_STATE_PAUSED_TO_PLAYING) {
    /* find the chain the element is in */
    chain = gst_basic_scheduler_find_chain (bsched, element);

    /* remove it from the chain */
    if (chain) {
      if (transition == GST_STATE_PLAYING_TO_PAUSED) {
        gst_basic_scheduler_chain_disable_element (chain, element);
      }
      else if (transition == GST_STATE_PAUSED_TO_PLAYING) {
        if (!gst_basic_scheduler_chain_enable_element (chain, element)) {
          GST_INFO (GST_CAT_SCHEDULING, "could not enable element \"%s\"", GST_ELEMENT_NAME (element));
          return GST_STATE_FAILURE;
        }
      }
    }
    else {
      GST_INFO (GST_CAT_SCHEDULING, "element \"%s\" not found in any chain, no state change", GST_ELEMENT_NAME (element));
    }
  }

  return GST_STATE_SUCCESS;
}

static void
gst_basic_scheduler_lock_element (GstScheduler * sched, GstElement * element)
{
  if (GST_ELEMENT_THREADSTATE (element))
    do_cothread_lock (GST_ELEMENT_THREADSTATE (element));
}

static void
gst_basic_scheduler_unlock_element (GstScheduler * sched, GstElement * element)
{
  if (GST_ELEMENT_THREADSTATE (element))
    do_cothread_unlock (GST_ELEMENT_THREADSTATE (element));
}

static void
gst_basic_scheduler_yield (GstScheduler *sched, GstElement *element)
{
  if (GST_ELEMENT_IS_COTHREAD_STOPPING (element)) {

    do_switch_to_main (sched);
    
    /* no need to do a pre_run, the cothread is stopping */
  }
}

static gboolean
gst_basic_scheduler_interrupt (GstScheduler *sched, GstElement *element)
{

  if (GST_FLAG_IS_SET (element, GST_ELEMENT_DECOUPLED)) {
    g_warning ("interrupt decoupled element");
  }

  GST_FLAG_SET (element, GST_ELEMENT_COTHREAD_STOPPING);
  do_switch_to_main (sched);
<commit_msg>Set the right scheduler on the pads of decoupled elements.<commit_after>/* GStreamer
 * Copyright (C) 1999,2000 <NAME> <<EMAIL>>
 *                    2000 <NAME> <<EMAIL>>
 *
 * gstscheduler.c: Default scheduling code for most cases
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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.
 */

/*#define GST_DEBUG_ENABLED */
#include "../gst.h"

#include "cothreads_compat.h"

typedef struct _GstSchedulerChain GstSchedulerChain;

#define GST_PAD_THREADSTATE(pad)	(cothread*) (GST_PAD_CAST (pad)->sched_private)
#define GST_ELEMENT_THREADSTATE(elem)	(cothread*) (GST_ELEMENT_CAST (elem)->sched_private)

#define GST_ELEMENT_COTHREAD_STOPPING			GST_ELEMENT_SCHEDULER_PRIVATE1
#define GST_ELEMENT_IS_COTHREAD_STOPPING(element)	GST_FLAG_IS_SET((element), GST_ELEMENT_COTHREAD_STOPPING)

typedef struct _GstBasicScheduler GstBasicScheduler;
typedef struct _GstBasicSchedulerClass GstBasicSchedulerClass;

struct _GstSchedulerChain {
  GstBasicScheduler *sched;

  GList *disabled;

  GList *elements;
  gint num_elements;

  GstElement *entry;

  gint cothreaded_elements;
  gboolean schedule;
};

#define GST_TYPE_BASIC_SCHEDULER \
  (gst_basic_scheduler_get_type())
#define GST_BASIC_SCHEDULER(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_BASIC_SCHEDULER,GstBasicScheduler))
#define GST_BASIC_SCHEDULER_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_BASIC_SCHEDULER,GstBasicSchedulerClass))
#define GST_IS_BASIC_SCHEDULER(obj) \
  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_BASIC_SCHEDULER))
#define GST_IS_BASIC_SCHEDULER_CLASS(obj) \
  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_BASIC_SCHEDULER))

#define GST_BASIC_SCHEDULER_CAST(sched)	((GstBasicScheduler *)(sched))
#define SCHED(element) GST_BASIC_SCHEDULER_CAST (GST_ELEMENT_SCHED (element))

typedef enum {
  GST_BASIC_SCHEDULER_STATE_NONE,
  GST_BASIC_SCHEDULER_STATE_STOPPED,
  GST_BASIC_SCHEDULER_STATE_ERROR,
  GST_BASIC_SCHEDULER_STATE_RUNNING,
} GstBasicSchedulerState;

typedef enum {
  /* something important has changed inside the scheduler */
  GST_BASIC_SCHEDULER_CHANGE	= GST_SCHEDULER_FLAG_LAST,
} GstBasicSchedulerFlags;

struct _GstBasicScheduler {
  GstScheduler parent;

  GList *elements;
  gint num_elements;

  GList *chains;
  gint num_chains;

  GstBasicSchedulerState state;
  
  cothread_context *context;
  GstElement *current;
};

struct _GstBasicSchedulerClass {
  GstSchedulerClass parent_class;
};

static GType _gst_basic_scheduler_type = 0;

static void 		gst_basic_scheduler_class_init 		(GstBasicSchedulerClass * klass);
static void 		gst_basic_scheduler_init 		(GstBasicScheduler * scheduler);

static void 		gst_basic_scheduler_dispose 		(GObject *object);

static void 		gst_basic_scheduler_setup 		(GstScheduler *sched);
static void 		gst_basic_scheduler_reset 		(GstScheduler *sched);
static void		gst_basic_scheduler_add_element		(GstScheduler *sched, GstElement *element);
static void     	gst_basic_scheduler_remove_element	(GstScheduler *sched, GstElement *element);
static GstElementStateReturn  
			gst_basic_scheduler_state_transition	(GstScheduler *sched, GstElement *element, gint transition);
static void 		gst_basic_scheduler_lock_element 	(GstScheduler *sched, GstElement *element);
static void 		gst_basic_scheduler_unlock_element 	(GstScheduler *sched, GstElement *element);
static void 		gst_basic_scheduler_yield 		(GstScheduler *sched, GstElement *element);
static gboolean		gst_basic_scheduler_interrupt 		(GstScheduler *sched, GstElement *element);
static void 		gst_basic_scheduler_error	 	(GstScheduler *sched, GstElement *element);
static void     	gst_basic_scheduler_pad_connect		(GstScheduler *sched, GstPad *srcpad, GstPad *sinkpad);
static void     	gst_basic_scheduler_pad_disconnect 	(GstScheduler *sched, GstPad *srcpad, GstPad *sinkpad);
static GstPad*  	gst_basic_scheduler_pad_select 		(GstScheduler *sched, GList *padlist);
static GstClockReturn	gst_basic_scheduler_clock_wait	 	(GstScheduler *sched, GstElement *element,
								 GstClock *clock, GstClockTime time, GstClockTimeDiff *jitter);
static GstSchedulerState
			gst_basic_scheduler_iterate    		(GstScheduler *sched);

static void     	gst_basic_scheduler_show  		(GstScheduler *sched);

static GstSchedulerClass *parent_class = NULL;

/* for threaded bins, these pre- and post-run functions lock and unlock the
 * elements. we have to avoid deadlocks, so we make these convenience macros
 * that will avoid using do_cothread_switch from within the scheduler. */

#define do_element_switch(element) G_STMT_START{		\
  GstElement *from = SCHED (element)->current;			\
  if (from && from->post_run_func)				\
    from->post_run_func (from);					\
  SCHED (element)->current = element;				\
  if (element->pre_run_func)					\
    element->pre_run_func (element);				\
  do_cothread_switch (GST_ELEMENT_THREADSTATE (element));	\
}G_STMT_END

#define do_switch_to_main(sched) G_STMT_START{			\
  GstElement *current = ((GstBasicScheduler*)sched)->current;	\
  if (current && current->post_run_func)			\
    current->post_run_func (current);				\
  SCHED (current)->current = NULL;				\
  do_cothread_switch						\
    (do_cothread_get_main					\
       (((GstBasicScheduler*)sched)->context));			\
}G_STMT_END

#define do_switch_from_main(entry) G_STMT_START{		\
  if (entry->pre_run_func)					\
    entry->pre_run_func (entry);				\
  SCHED (entry)->current = entry;				\
  do_cothread_switch (GST_ELEMENT_THREADSTATE (entry));		\
}G_STMT_END

static GType
gst_basic_scheduler_get_type (void)
{
  if (!_gst_basic_scheduler_type) {
    static const GTypeInfo scheduler_info = {
      sizeof (GstBasicSchedulerClass),
      NULL,
      NULL,
      (GClassInitFunc) gst_basic_scheduler_class_init,
      NULL,
      NULL,
      sizeof (GstBasicScheduler),
      0,
      (GInstanceInitFunc) gst_basic_scheduler_init,
      NULL
    };

    _gst_basic_scheduler_type = g_type_register_static (GST_TYPE_SCHEDULER, "Gst"COTHREADS_NAME_CAPITAL"Scheduler", &scheduler_info, 0);
  }
  return _gst_basic_scheduler_type;
}

static void
gst_basic_scheduler_class_init (GstBasicSchedulerClass * klass)
{
  GObjectClass *gobject_class;
  GstObjectClass *gstobject_class;
  GstSchedulerClass *gstscheduler_class;

  gobject_class = (GObjectClass*)klass;
  gstobject_class = (GstObjectClass*)klass;
  gstscheduler_class = (GstSchedulerClass*)klass;

  parent_class = g_type_class_ref (GST_TYPE_SCHEDULER);

  gobject_class->dispose	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_dispose);

  gstscheduler_class->setup 		= GST_DEBUG_FUNCPTR (gst_basic_scheduler_setup);
  gstscheduler_class->reset	 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_reset);
  gstscheduler_class->add_element 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_add_element);
  gstscheduler_class->remove_element 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_remove_element);
  gstscheduler_class->state_transition 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_state_transition);
  gstscheduler_class->lock_element 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_lock_element);
  gstscheduler_class->unlock_element 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_unlock_element);
  gstscheduler_class->yield	 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_yield);
  gstscheduler_class->interrupt 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_interrupt);
  gstscheduler_class->error	 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_error);
  gstscheduler_class->pad_connect 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_pad_connect);
  gstscheduler_class->pad_disconnect 	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_pad_disconnect);
  gstscheduler_class->pad_select	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_pad_select);
  gstscheduler_class->clock_wait	= GST_DEBUG_FUNCPTR (gst_basic_scheduler_clock_wait);
  gstscheduler_class->iterate 		= GST_DEBUG_FUNCPTR (gst_basic_scheduler_iterate);

  gstscheduler_class->show 		= GST_DEBUG_FUNCPTR (gst_basic_scheduler_show);
  
  do_cothreads_init(NULL);
}

static void
gst_basic_scheduler_init (GstBasicScheduler *scheduler)
{
  scheduler->elements = NULL;
  scheduler->num_elements = 0;
  scheduler->chains = NULL;
  scheduler->num_chains = 0;
}

static void
gst_basic_scheduler_dispose (GObject *object)
{
  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static gboolean
plugin_init (GModule *module, GstPlugin *plugin)
{
  GstSchedulerFactory *factory;

  gst_plugin_set_longname (plugin, "A basic scheduler");

  factory = gst_scheduler_factory_new (COTHREADS_NAME,
	                              "A basic scheduler, it uses "COTHREADS_NAME" cothreads",
		                      gst_basic_scheduler_get_type());

  if (factory != NULL) {
    gst_plugin_add_feature (plugin, GST_PLUGIN_FEATURE (factory));
  }
  else {
    g_warning ("could not register scheduler: "COTHREADS_NAME);
  }
  return TRUE;
}

GstPluginDesc plugin_desc = {
  GST_VERSION_MAJOR,
  GST_VERSION_MINOR,
  "gst"COTHREADS_NAME"scheduler",
  plugin_init
};

static int
gst_basic_scheduler_loopfunc_wrapper (int argc, char *argv[])
{
  GstElement *element = GST_ELEMENT_CAST (argv);
  G_GNUC_UNUSED const gchar *name = GST_ELEMENT_NAME (element);

  GST_DEBUG_ENTER ("(%d,'%s')", argc, name);

  do {
    GST_DEBUG (GST_CAT_DATAFLOW, "calling loopfunc %s for element %s",
	       GST_DEBUG_FUNCPTR_NAME (element->loopfunc), name);
    (element->loopfunc) (element);
    GST_DEBUG (GST_CAT_DATAFLOW, "element %s ended loop function", name);

  } while (!GST_ELEMENT_IS_COTHREAD_STOPPING (element));
  GST_FLAG_UNSET (element, GST_ELEMENT_COTHREAD_STOPPING);

  GST_DEBUG_LEAVE ("(%d,'%s')", argc, name);
  return 0;
}

static int
gst_basic_scheduler_chain_wrapper (int argc, char *argv[])
{
  GstElement *element = GST_ELEMENT_CAST (argv);
  G_GNUC_UNUSED const gchar *name = GST_ELEMENT_NAME (element);

  GST_DEBUG_ENTER ("(\"%s\")", name);

  GST_DEBUG (GST_CAT_DATAFLOW, "stepping through pads");

  gst_object_ref (GST_OBJECT (element));
  do {
    GList *pads = element->pads;

    while (pads) {
      GstPad *pad = GST_PAD (pads->data);
      GstRealPad *realpad;

      pads = g_list_next (pads);
      if (!GST_IS_REAL_PAD (pad))
	continue;

      realpad = GST_REAL_PAD_CAST (pad);

      if (GST_RPAD_DIRECTION (realpad) == GST_PAD_SINK) {
	GstBuffer *buf;

	GST_DEBUG (GST_CAT_DATAFLOW, "pulling data from %s:%s", name, GST_PAD_NAME (pad));
	buf = gst_pad_pull (pad);
	if (buf) {
	  if (GST_IS_EVENT (buf) && !GST_ELEMENT_IS_EVENT_AWARE (element)) {
	    gst_pad_send_event (pad, GST_EVENT (buf));
	  }
	  else {
	    GST_DEBUG (GST_CAT_DATAFLOW, "calling chain function of %s:%s", name,
		       GST_PAD_NAME (pad));
	    GST_RPAD_CHAINFUNC (realpad) (pad, buf);
	    GST_DEBUG (GST_CAT_DATAFLOW, "calling chain function of element %s done", name);
	  }
	}
      }
    }
  } while (!GST_ELEMENT_IS_COTHREAD_STOPPING (element));
  GST_FLAG_UNSET (element, GST_ELEMENT_COTHREAD_STOPPING);

  /* due to oddities in the cothreads code, when this function returns it will
   * switch to the main cothread. thus, we need to unlock the current element. */
  if (SCHED (element)) {
    if (SCHED (element)->current && SCHED (element)->current->post_run_func) {
      SCHED (element)->current->post_run_func (SCHED (element)->current);
    }
    SCHED (element)->current = NULL;
  }

  GST_DEBUG_LEAVE ("(%d,'%s')", argc, name);
  gst_object_unref (GST_OBJECT (element));
  return 0;
}

static int
gst_basic_scheduler_src_wrapper (int argc, char *argv[])
{
  GstElement *element = GST_ELEMENT_CAST (argv);
  GList *pads;
  GstRealPad *realpad;
  GstBuffer *buf = NULL;
  G_GNUC_UNUSED const gchar *name = GST_ELEMENT_NAME (element);

  GST_DEBUG_ENTER ("(%d,\"%s\")", argc, name);

  do {
    pads = element->pads;
    while (pads) {

      if (!GST_IS_REAL_PAD (pads->data))
	continue;

      realpad = GST_REAL_PAD_CAST (pads->data);

      pads = g_list_next (pads);
      if (GST_RPAD_DIRECTION (realpad) == GST_PAD_SRC) {
	GST_DEBUG (GST_CAT_DATAFLOW, "calling _getfunc for %s:%s", GST_DEBUG_PAD_NAME (realpad));
	g_return_val_if_fail (GST_RPAD_GETFUNC (realpad) != NULL, 0);
	buf = GST_RPAD_GETFUNC (realpad) (GST_PAD_CAST (realpad));
	if (buf) {
	  GST_DEBUG (GST_CAT_DATAFLOW, "calling gst_pad_push on pad %s:%s",
		     GST_DEBUG_PAD_NAME (realpad));
	  gst_pad_push (GST_PAD_CAST (realpad), buf);
	}
      }
    }
  } while (!GST_ELEMENT_IS_COTHREAD_STOPPING (element));

  GST_FLAG_UNSET (element, GST_ELEMENT_COTHREAD_STOPPING);

  /* due to oddities in the cothreads code, when this function returns it will
   * switch to the main cothread. thus, we need to unlock the current element. */
  if (SCHED (element)->current->post_run_func)
    SCHED (element)->current->post_run_func (SCHED (element)->current);
  SCHED (element)->current = NULL;

  GST_DEBUG_LEAVE ("");
  return 0;
}

static void
gst_basic_scheduler_chainhandler_proxy (GstPad * pad, GstBuffer * buf)
{
  gint loop_count = 100;
  GstElement *parent;
  GstRealPad *peer;

  parent = GST_PAD_PARENT (pad);
  peer = GST_RPAD_PEER (pad);

  GST_DEBUG_ENTER ("(%s:%s)", GST_DEBUG_PAD_NAME (pad));
  GST_DEBUG (GST_CAT_DATAFLOW, "putting buffer %p in peer \"%s:%s\"'s pen", buf,
	     GST_DEBUG_PAD_NAME (peer));

  /* 
   * loop until the bufferpen is empty so we can fill it up again
   */
  while (GST_RPAD_BUFPEN (GST_RPAD_PEER (pad)) != NULL && --loop_count) {
    GST_DEBUG (GST_CAT_DATAFLOW, "switching to %p to empty bufpen %d",
	       GST_ELEMENT_THREADSTATE (parent), loop_count);

    do_element_switch (parent);

    /* we may no longer be the same pad, check. */
    if (GST_RPAD_PEER (peer) != (GstRealPad *) pad) {
      GST_DEBUG (GST_CAT_DATAFLOW, "new pad in mid-switch!");
      pad = (GstPad *) GST_RPAD_PEER (peer);
    }
    parent = GST_PAD_PARENT (pad);
    peer = GST_RPAD_PEER (pad);
  }

  if (loop_count == 0) {
    gst_element_error (parent, 
		    "(internal error) maximum number of switches exceeded");
    return;
  }

  g_assert (GST_RPAD_BUFPEN (GST_RPAD_PEER (pad)) == NULL);

  /* now fill the bufferpen and switch so it can be consumed */
  GST_RPAD_BUFPEN (GST_RPAD_PEER (pad)) = buf;
  GST_DEBUG (GST_CAT_DATAFLOW, "switching to %p",
	     GST_ELEMENT_THREADSTATE (GST_PAD_PARENT (pad)));

  do_element_switch (parent);

  GST_DEBUG (GST_CAT_DATAFLOW, "done switching");
}

static void
gst_basic_scheduler_select_proxy (GstPad * pad, GstBuffer * buf)
{
  GstElement *parent;
  
  parent = GST_PAD_PARENT (pad);

  GST_DEBUG_ENTER ("(%s:%s)", GST_DEBUG_PAD_NAME (pad));

  GST_DEBUG (GST_CAT_DATAFLOW, "putting buffer %p in peer's pen", buf);

  g_assert (GST_RPAD_BUFPEN (GST_RPAD_PEER (pad)) == NULL);
  /* now fill the bufferpen and switch so it can be consumed */
  GST_RPAD_BUFPEN (GST_RPAD_PEER (pad)) = buf;
  GST_DEBUG (GST_CAT_DATAFLOW, "switching to %p",
	     GST_ELEMENT_THREADSTATE (parent));
  parent->select_pad = pad;

  do_element_switch (parent);
  
  GST_DEBUG (GST_CAT_DATAFLOW, "done switching");
}


static GstBuffer *
gst_basic_scheduler_gethandler_proxy (GstPad * pad)
{
  GstBuffer *buf;
  GstElement *parent;
  GstRealPad *peer;

  GST_DEBUG_ENTER ("(%s:%s)", GST_DEBUG_PAD_NAME (pad));

  parent = GST_PAD_PARENT (pad);
  peer = GST_RPAD_PEER (pad);

  /* FIXME this should be bounded */
  /* we will loop switching to the peer until it's filled up the bufferpen */
  while (GST_RPAD_BUFPEN (pad) == NULL) {

    GST_DEBUG (GST_CAT_DATAFLOW, "switching to \"%s\": %p to fill bufpen",
	       GST_ELEMENT_NAME (parent),
	       GST_ELEMENT_THREADSTATE (parent));

    do_element_switch (parent);

    /* we may no longer be the same pad, check. */
    if (GST_RPAD_PEER (peer) != (GstRealPad *) pad) {
      GST_DEBUG (GST_CAT_DATAFLOW, "new pad in mid-switch!");
      pad = (GstPad *) GST_RPAD_PEER (peer);
      if (!pad) {
	gst_element_error (parent, "pad unconnected");
      }
      parent = GST_PAD_PARENT (pad);
      peer = GST_RPAD_PEER (pad);
    }
  }
  GST_DEBUG (GST_CAT_DATAFLOW, "done switching");

  /* now grab the buffer from the pen, clear the pen, and return the buffer */
  buf = GST_RPAD_BUFPEN (pad);
  GST_RPAD_BUFPEN (pad) = NULL;

  return buf;
}

static gboolean
gst_basic_scheduler_cothreaded_chain (GstBin * bin, GstSchedulerChain * chain)
{
  GList *elements;
  GstElement *element;
  cothread_func wrapper_function;
  GList *pads;
  GstPad *pad;

  GST_DEBUG (GST_CAT_SCHEDULING, "chain is using COTHREADS");

  g_assert (chain->sched->context != NULL);

  /* walk through all the chain's elements */
  elements = chain->elements;
  while (elements) {
    gboolean decoupled;

    element = GST_ELEMENT_CAST (elements->data);
    elements = g_list_next (elements);

    decoupled = (GST_FLAG_IS_SET (element, GST_ELEMENT_DECOUPLED) ? TRUE :  FALSE);

    /* start out without a wrapper function, we select it later */
    wrapper_function = NULL;

    /* if the element has a loopfunc... */
    if (element->loopfunc != NULL) {
      wrapper_function = GST_DEBUG_FUNCPTR (gst_basic_scheduler_loopfunc_wrapper);
      GST_DEBUG (GST_CAT_SCHEDULING, "element '%s' is a loop-based", GST_ELEMENT_NAME (element));
    }
    else {
      /* otherwise we need to decide what kind of cothread */
      /* if it's not DECOUPLED, we decide based on whether it's a source or not */
      if (!decoupled) {
	/* if it doesn't have any sinks, it must be a source (duh) */
	if (element->numsinkpads == 0) {
	  wrapper_function = GST_DEBUG_FUNCPTR (gst_basic_scheduler_src_wrapper);
	  GST_DEBUG (GST_CAT_SCHEDULING, "element '%s' is a source, using _src_wrapper",
		     GST_ELEMENT_NAME (element));
	}
	else {
	  wrapper_function = GST_DEBUG_FUNCPTR (gst_basic_scheduler_chain_wrapper);
	  GST_DEBUG (GST_CAT_SCHEDULING, "element '%s' is a filter, using _chain_wrapper",
		     GST_ELEMENT_NAME (element));
	}
      }
    }

    /* now we have to walk through the pads to set up their state */
    pads = gst_element_get_pad_list (element);
    while (pads) {
      GstPad *peerpad;

      pad = GST_PAD (pads->data);
      pads = g_list_next (pads);

      if (!GST_IS_REAL_PAD (pad))
	continue;
      
      peerpad = GST_PAD_PEER (pad);
      if (peerpad) {
	GstElement *peerelement = GST_ELEMENT_CAST (GST_PAD_PARENT (peerpad));
	gboolean different_sched = (peerelement->sched != GST_SCHEDULER (chain->sched));
	gboolean peer_decoupled = GST_FLAG_IS_SET (peerelement, GST_ELEMENT_DECOUPLED);

        GST_DEBUG (GST_CAT_SCHEDULING, "inspecting pad %s:%s", GST_DEBUG_PAD_NAME (peerpad));

	/* we don't need to check this for decoupled elements */
	if (!decoupled) {
	  /* if the peer element is in another schedule, it's not decoupled and we are not decoupled
	   * either, we have an error */
	  if (different_sched && !peer_decoupled) 
 	  {
            gst_element_error (element, "element \"%s\" is not decoupled but has pads in different schedulers",
			  GST_ELEMENT_NAME (element), NULL);
	    return FALSE;
	  }
	  /* ok, the peer is in a different scheduler and is decoupled, we need to set the
	   * handlers so we can talk with it */
	  else if (different_sched) {
	    if (GST_RPAD_DIRECTION (peerpad) == GST_PAD_SINK) {
	      GST_DEBUG (GST_CAT_SCHEDULING, "copying chain function into push proxy for peer %s:%s",
		     GST_DEBUG_PAD_NAME (peerpad));
	      GST_RPAD_CHAINHANDLER (peerpad) = GST_RPAD_CHAINFUNC (peerpad);
	    }
	    else {
	      GST_DEBUG (GST_CAT_SCHEDULING, "copying get function into pull proxy for peer %s:%s",
		     GST_DEBUG_PAD_NAME (peerpad));
	      GST_RPAD_GETHANDLER (peerpad) = GST_RPAD_GETFUNC (peerpad);
	    }
	    gst_pad_set_scheduler (peerpad, GST_SCHEDULER (chain->sched));
	  }
	}
      }

      /* if the element is DECOUPLED or outside the manager, we have to chain */
      if (decoupled) {
	/* set the chain proxies */
	if (GST_RPAD_DIRECTION (pad) == GST_PAD_SINK) {
	  GST_DEBUG (GST_CAT_SCHEDULING, "copying chain function into push proxy for %s:%s",
		     GST_DEBUG_PAD_NAME (pad));
	  GST_RPAD_CHAINHANDLER (pad) = GST_RPAD_CHAINFUNC (pad);
	}
	else {
	  GST_DEBUG (GST_CAT_SCHEDULING, "copying get function into pull proxy for %s:%s",
		     GST_DEBUG_PAD_NAME (pad));
	  GST_RPAD_GETHANDLER (pad) = GST_RPAD_GETFUNC (pad);
	}
      }
      /* otherwise we really are a cothread */
      else {
	if (GST_RPAD_DIRECTION (pad) == GST_PAD_SINK) {
	  GST_DEBUG (GST_CAT_SCHEDULING, "setting cothreaded push proxy for sinkpad %s:%s",
	     GST_DEBUG_PAD_NAME (pad));
	  GST_RPAD_CHAINHANDLER (pad) = GST_DEBUG_FUNCPTR (gst_basic_scheduler_chainhandler_proxy);
	}
	else {
	  GST_DEBUG (GST_CAT_SCHEDULING, "setting cothreaded pull proxy for srcpad %s:%s",
	     GST_DEBUG_PAD_NAME (pad));
	  GST_RPAD_GETHANDLER (pad) = GST_DEBUG_FUNCPTR (gst_basic_scheduler_gethandler_proxy);
	}
      }
    }

    /* need to set up the cothread now */
    if (wrapper_function != NULL) {
      if (GST_ELEMENT_THREADSTATE (element) == NULL) {
	do_cothread_create (GST_ELEMENT_THREADSTATE (element), chain->sched->context, 
			    wrapper_function, 0, (char **) element);
	if (GST_ELEMENT_THREADSTATE (element) == NULL) {
          gst_element_error (element, "could not create cothread for \"%s\"", 
			  GST_ELEMENT_NAME (element), NULL);
	  return FALSE;
	}
	GST_DEBUG (GST_CAT_SCHEDULING, "created cothread %p for '%s'", 
		   GST_ELEMENT_THREADSTATE (element),
		   GST_ELEMENT_NAME (element));
      } else {
	do_cothread_reset (GST_ELEMENT_THREADSTATE (element), chain->sched->context, 
			   wrapper_function, 0, (char **) element);
	GST_DEBUG (GST_CAT_SCHEDULING, "set wrapper function for '%s' to &%s",
		   GST_ELEMENT_NAME (element), GST_DEBUG_FUNCPTR_NAME (wrapper_function));
      }
    }
  }

  return TRUE;
}

static GstSchedulerChain *
gst_basic_scheduler_chain_new (GstBasicScheduler * sched)
{
  GstSchedulerChain *chain = g_new (GstSchedulerChain, 1);

  /* initialize the chain with sane values */
  chain->sched = sched;
  chain->disabled = NULL;
  chain->elements = NULL;
  chain->num_elements = 0;
  chain->entry = NULL;
  chain->cothreaded_elements = 0;
  chain->schedule = FALSE;

  /* add the chain to the schedulers' list of chains */
  sched->chains = g_list_prepend (sched->chains, chain);
  sched->num_chains++;

  /* notify the scheduler that something changed */
  GST_FLAG_SET(sched, GST_BASIC_SCHEDULER_CHANGE);

  GST_INFO (GST_CAT_SCHEDULING, "created new chain %p, now are %d chains in sched %p",
	    chain, sched->num_chains, sched);

  return chain;
}

static void
gst_basic_scheduler_chain_destroy (GstSchedulerChain * chain)
{
  GstBasicScheduler *sched = chain->sched;

  /* remove the chain from the schedulers' list of chains */
  sched->chains = g_list_remove (sched->chains, chain);
  sched->num_chains--;

  /* destroy the chain */
  g_list_free (chain->disabled);	/* should be empty... */
  g_list_free (chain->elements);	/* ditto 	      */

  GST_INFO (GST_CAT_SCHEDULING, "destroyed chain %p, now are %d chains in sched %p", chain,
	    sched->num_chains, sched);

  g_free (chain);

  /* notify the scheduler that something changed */
  GST_FLAG_SET(sched, GST_BASIC_SCHEDULER_CHANGE);
}

static void
gst_basic_scheduler_chain_add_element (GstSchedulerChain * chain, GstElement * element)
{
  GST_INFO (GST_CAT_SCHEDULING, "adding element \"%s\" to chain %p", GST_ELEMENT_NAME (element),
	    chain);

  /* set the sched pointer for the element */
  element->sched = GST_SCHEDULER (chain->sched);

  /* add the element to the list of 'disabled' elements */
  chain->disabled = g_list_prepend (chain->disabled, element);
  chain->num_elements++;

  /* notify the scheduler that something changed */
  GST_FLAG_SET(chain->sched, GST_BASIC_SCHEDULER_CHANGE);
}

static gboolean
gst_basic_scheduler_chain_enable_element (GstSchedulerChain * chain, GstElement * element)
{
  GST_INFO (GST_CAT_SCHEDULING, "enabling element \"%s\" in chain %p", GST_ELEMENT_NAME (element),
	    chain);

  /* remove from disabled list */
  chain->disabled = g_list_remove (chain->disabled, element);

  /* add to elements list */
  chain->elements = g_list_prepend (chain->elements, element);

  /* notify the scheduler that something changed */
  GST_FLAG_SET(chain->sched, GST_BASIC_SCHEDULER_CHANGE);
  /* GST_FLAG_UNSET(element, GST_ELEMENT_COTHREAD_STOPPING); */

  /* reschedule the chain */
  return gst_basic_scheduler_cothreaded_chain (GST_BIN (GST_SCHEDULER (chain->sched)->parent), chain);
}

static void
gst_basic_scheduler_chain_disable_element (GstSchedulerChain * chain, GstElement * element)
{
  GST_INFO (GST_CAT_SCHEDULING, "disabling element \"%s\" in chain %p", GST_ELEMENT_NAME (element),
	    chain);

  /* remove from elements list */
  chain->elements = g_list_remove (chain->elements, element);

  /* add to disabled list */
  chain->disabled = g_list_prepend (chain->disabled, element);

  /* notify the scheduler that something changed */
  GST_FLAG_SET(chain->sched, GST_BASIC_SCHEDULER_CHANGE);
  GST_FLAG_SET(element, GST_ELEMENT_COTHREAD_STOPPING);

  /* reschedule the chain */
/* FIXME this should be done only if manager state != NULL */
/*  gst_basic_scheduler_cothreaded_chain(GST_BIN(chain->sched->parent),chain); */
}

static void
gst_basic_scheduler_chain_remove_element (GstSchedulerChain * chain, GstElement * element)
{
  GST_INFO (GST_CAT_SCHEDULING, "removing element \"%s\" from chain %p", GST_ELEMENT_NAME (element),
	    chain);

  /* if it's active, deactivate it */
  if (g_list_find (chain->elements, element)) {
    gst_basic_scheduler_chain_disable_element (chain, element);
  }
  /* we have to check for a threadstate here because a queue doesn't have one */
  if (GST_ELEMENT_THREADSTATE (element)) {
    do_cothread_destroy (GST_ELEMENT_THREADSTATE (element));
    GST_ELEMENT_THREADSTATE (element) = NULL;
  }

  /* remove the element from the list of elements */
  chain->disabled = g_list_remove (chain->disabled, element);
  chain->num_elements--;

  /* notify the scheduler that something changed */
  GST_FLAG_SET(chain->sched, GST_BASIC_SCHEDULER_CHANGE);

  /* if there are no more elements in the chain, destroy the chain */
  if (chain->num_elements == 0)
    gst_basic_scheduler_chain_destroy (chain);

}

static void
gst_basic_scheduler_chain_elements (GstBasicScheduler * sched, GstElement * element1, GstElement * element2)
{
  GList *chains;
  GstSchedulerChain *chain;
  GstSchedulerChain *chain1 = NULL, *chain2 = NULL;
  GstElement *element;

  /* first find the chains that hold the two  */
  chains = sched->chains;
  while (chains) {
    chain = (GstSchedulerChain *) (chains->data);
    chains = g_list_next (chains);

    if (g_list_find (chain->disabled, element1))
      chain1 = chain;
    else if (g_list_find (chain->elements, element1))
      chain1 = chain;

    if (g_list_find (chain->disabled, element2))
      chain2 = chain;
    else if (g_list_find (chain->elements, element2))
      chain2 = chain;
  }

  /* first check to see if they're in the same chain, we're done if that's the case */
  if ((chain1 != NULL) && (chain1 == chain2)) {
    GST_INFO (GST_CAT_SCHEDULING, "elements are already in the same chain");
    return;
  }

  /* now, if neither element has a chain, create one */
  if ((chain1 == NULL) && (chain2 == NULL)) {
    GST_INFO (GST_CAT_SCHEDULING, "creating new chain to hold two new elements");
    chain = gst_basic_scheduler_chain_new (sched);
    gst_basic_scheduler_chain_add_element (chain, element1);
    gst_basic_scheduler_chain_add_element (chain, element2);
    /* FIXME chain changed here */
/*    gst_basic_scheduler_cothreaded_chain(chain->sched->parent,chain); */

    /* otherwise if both have chains already, join them */
  }
  else if ((chain1 != NULL) && (chain2 != NULL)) {
    GST_INFO (GST_CAT_SCHEDULING, "merging chain %p into chain %p", chain2, chain1);
    /* take the contents of chain2 and merge them into chain1 */
    chain1->disabled = g_list_concat (chain1->disabled, g_list_copy (chain2->disabled));
    chain1->elements = g_list_concat (chain1->elements, g_list_copy (chain2->elements));
    chain1->num_elements += chain2->num_elements;
    /* FIXME chain changed here */
/*    gst_basic_scheduler_cothreaded_chain(chain->sched->parent,chain); */

    gst_basic_scheduler_chain_destroy (chain2);

    /* otherwise one has a chain already, the other doesn't */
  }
  else {
    /* pick out which one has the chain, and which doesn't */
    if (chain1 != NULL)
      chain = chain1, element = element2;
    else
      chain = chain2, element = element1;

    GST_INFO (GST_CAT_SCHEDULING, "adding element to existing chain");
    gst_basic_scheduler_chain_add_element (chain, element);
    /* FIXME chain changed here */
/*    gst_basic_scheduler_cothreaded_chain(chain->sched->parent,chain); */
  }

}


/* find the chain within the scheduler that holds the element, if any */
static GstSchedulerChain *
gst_basic_scheduler_find_chain (GstBasicScheduler * sched, GstElement * element)
{
  GList *chains;
  GstSchedulerChain *chain;

  GST_INFO (GST_CAT_SCHEDULING, "searching for element \"%s\" in chains",
	    GST_ELEMENT_NAME (element));

  chains = sched->chains;
  while (chains) {
    chain = (GstSchedulerChain *) (chains->data);
    chains = g_list_next (chains);

    if (g_list_find (chain->elements, element))
      return chain;
    if (g_list_find (chain->disabled, element))
      return chain;
  }

  return NULL;
}

static void
gst_basic_scheduler_chain_recursive_add (GstSchedulerChain * chain, GstElement * element)
{
  GList *pads;
  GstPad *pad;
  GstElement *peerelement;

  /* add the element to the chain */
  gst_basic_scheduler_chain_add_element (chain, element);

  GST_DEBUG (GST_CAT_SCHEDULING, "recursing on element \"%s\"", GST_ELEMENT_NAME (element));
  /* now go through all the pads and see which peers can be added */
  pads = element->pads;
  while (pads) {
    pad = GST_PAD (pads->data);
    pads = g_list_next (pads);

    GST_DEBUG (GST_CAT_SCHEDULING, "have pad %s:%s, checking for valid peer",
	       GST_DEBUG_PAD_NAME (pad));
    /* if the peer exists and could be in the same chain */
    if (GST_PAD_PEER (pad)) {
      GST_DEBUG (GST_CAT_SCHEDULING, "has peer %s:%s", GST_DEBUG_PAD_NAME (GST_PAD_PEER (pad)));
      peerelement = GST_PAD_PARENT (GST_PAD_PEER (pad));
      if (GST_ELEMENT_SCHED (GST_PAD_PARENT (pad)) == GST_ELEMENT_SCHED (peerelement)) {
	GST_DEBUG (GST_CAT_SCHEDULING, "peer \"%s\" is valid for same chain",
		   GST_ELEMENT_NAME (peerelement));
	/* if it's not already in a chain, add it to this one */
	if (gst_basic_scheduler_find_chain (chain->sched, peerelement) == NULL) {
	  gst_basic_scheduler_chain_recursive_add (chain, peerelement);
	}
      }
    }
  }
}

/*
 * Entry points for this scheduler.
 */
static void
gst_basic_scheduler_setup (GstScheduler *sched)
{

  /* first create thread context */
  if (GST_BASIC_SCHEDULER_CAST (sched)->context == NULL) {
    GST_DEBUG (GST_CAT_SCHEDULING, "initializing cothread context");
    GST_BASIC_SCHEDULER_CAST (sched)->context = do_cothread_context_init ();
  }
}

static void
gst_basic_scheduler_reset (GstScheduler *sched)
{
  cothread_context *ctx;
  GList *elements = GST_BASIC_SCHEDULER_CAST (sched)->elements;

  while (elements) {
    /* FIXME: wingo, do we need to destroy the cothreads here? */
    GST_ELEMENT_THREADSTATE (elements->data) = NULL;
    elements = g_list_next (elements);
  }
  
  ctx = GST_BASIC_SCHEDULER_CAST (sched)->context;

  do_cothread_context_destroy (ctx);
  
  GST_BASIC_SCHEDULER_CAST (sched)->context = NULL;
}

static void
gst_basic_scheduler_add_element (GstScheduler * sched, GstElement * element)
{
  GList *pads;
  GstPad *pad;
  GstElement *peerelement;
  GstSchedulerChain *chain;
  GstBasicScheduler *bsched = GST_BASIC_SCHEDULER (sched);

  /* if it's already in this scheduler, don't bother doing anything */
  if (GST_ELEMENT_SCHED (element) == sched)
    return;

  GST_INFO (GST_CAT_SCHEDULING, "adding element \"%s\" to scheduler", GST_ELEMENT_NAME (element));

  /* if the element already has a scheduler something went wrong */
  if (GST_ELEMENT_SCHED (element)) {
    GST_ERROR (element, "grave error");
    return;
  }
  
  /* set the sched pointer in the element itself */
  GST_ELEMENT_SCHED (element) = sched;

  /* only deal with elements after this point, not bins */
  /* exception is made for Bin's that are schedulable, like the autoplugger */
  if (GST_IS_BIN (element) && !GST_FLAG_IS_SET (element, GST_BIN_SELF_SCHEDULABLE))
    return;

  /* first add it to the list of elements that are to be scheduled */
  bsched->elements = g_list_prepend (bsched->elements, element);
  bsched->num_elements++;

  /* create a chain to hold it, and add */
  chain = gst_basic_scheduler_chain_new (bsched);
  gst_basic_scheduler_chain_add_element (chain, element);

  /* set the sched pointer in all the pads */
  pads = element->pads;
  while (pads) {
    pad = GST_PAD (pads->data);
    pads = g_list_next (pads);

    /* we only operate on real pads */
    if (!GST_IS_REAL_PAD (pad))
      continue;

    /* set the pad's sched pointer */
    gst_pad_set_scheduler (pad, sched);

    /* if the peer element exists and is a candidate */
    if (GST_PAD_PEER (pad)) {
      peerelement = GST_PAD_PARENT (GST_PAD_PEER (pad));
      if (GST_ELEMENT_SCHED (element) == GST_ELEMENT_SCHED (peerelement)) {
	GST_INFO (GST_CAT_SCHEDULING, "peer is in same scheduler, chaining together");
	/* make sure that the two elements are in the same chain */
	gst_basic_scheduler_chain_elements (bsched, element, peerelement);
      }
    }
  }
}

static void
gst_basic_scheduler_remove_element (GstScheduler * sched, GstElement * element)
{
  GstSchedulerChain *chain;
  GstBasicScheduler *bsched = GST_BASIC_SCHEDULER (sched);

  if (g_list_find (bsched->elements, element)) {
    GST_INFO (GST_CAT_SCHEDULING, "removing element \"%s\" from scheduler",
	      GST_ELEMENT_NAME (element));

    /* if we are removing the currently scheduled element */
    if (bsched->current == element) {
       GST_FLAG_SET(element, GST_ELEMENT_COTHREAD_STOPPING);
       if (element->post_run_func)
         element->post_run_func (element);
       bsched->current = NULL;
    }
    /* find what chain the element is in */
    chain = gst_basic_scheduler_find_chain (bsched, element);

    /* remove it from its chain */
    gst_basic_scheduler_chain_remove_element (chain, element);

    /* remove it from the list of elements */
    bsched->elements = g_list_remove (bsched->elements, element);
    bsched->num_elements--;

    /* unset the scheduler pointer in the element */
    GST_ELEMENT_SCHED (element) = NULL;
  }
}

static GstElementStateReturn
gst_basic_scheduler_state_transition (GstScheduler *sched, GstElement *element, gint transition)
{
  GstSchedulerChain *chain;
  GstBasicScheduler *bsched = GST_BASIC_SCHEDULER (sched);

  /* check if our parent changed state */
  if (GST_SCHEDULER_PARENT (sched) == element) {
    GST_INFO (GST_CAT_SCHEDULING, "parent \"%s\" changed state", GST_ELEMENT_NAME (element));
    if (transition == GST_STATE_PLAYING_TO_PAUSED) {
      GST_INFO (GST_CAT_SCHEDULING, "setting scheduler state to stopped");
      GST_SCHEDULER_STATE (sched) = GST_SCHEDULER_STATE_STOPPED;
    }
    else if (transition == GST_STATE_PAUSED_TO_PLAYING) {
      GST_INFO (GST_CAT_SCHEDULING, "setting scheduler state to running");
      GST_SCHEDULER_STATE (sched) = GST_SCHEDULER_STATE_RUNNING;
    }
    else {
      GST_INFO (GST_CAT_SCHEDULING, "no interesting state change, doing nothing");
    }
  }
  else if (transition == GST_STATE_PLAYING_TO_PAUSED ||
           transition == GST_STATE_PAUSED_TO_PLAYING) {
    /* find the chain the element is in */
    chain = gst_basic_scheduler_find_chain (bsched, element);

    /* remove it from the chain */
    if (chain) {
      if (transition == GST_STATE_PLAYING_TO_PAUSED) {
        gst_basic_scheduler_chain_disable_element (chain, element);
      }
      else if (transition == GST_STATE_PAUSED_TO_PLAYING) {
        if (!gst_basic_scheduler_chain_enable_element (chain, element)) {
          GST_INFO (GST_CAT_SCHEDULING, "could not enable element \"%s\"", GST_ELEMENT_NAME (element));
          return GST_STATE_FAILURE;
        }
      }
    }
    else {
      GST_INFO (GST_CAT_SCHEDULING, "element \"%s\" not found in any chain, no state change", GST_ELEMENT_NAME (element));
    }
  }

  return GST_STATE_SUCCESS;
}

static void
gst_basic_scheduler_lock_element (GstScheduler * sched, GstElement * element)
{
  if (GST_ELEMENT_THREADSTATE (element))
    do_cothread_lock (GST_ELEMENT_THREADSTATE (element));
}

static void
gst_basic_scheduler_unlock_element (GstScheduler * sched, GstElement * element)
{
  if (GST_ELEMENT_THREADSTATE (element))
    do_cothread_unlock (GST_ELEMENT_THREADSTATE (element));
}

static void
gst_basic_scheduler_yield (GstScheduler *sched, GstElement *element)
{
  if (GST_ELEMENT_IS_COTHREAD_STOPPING (element)) {

    do_switch_to_main (sched);
    
    /* no need to do a pre_run, the cothread is stopping */
  }
}

static gboolean
gst_basic_scheduler_interrupt (GstScheduler *sched, GstElement *element)
{

  GST_FLAG_SET (element, GST_ELEMENT_COTHREAD_STOPPING);
  do_switch_to_main (sched);

{'fraction_non_alphanumeric': 0.10358159549817941, 'fraction_numerical': 0.0023038728897715986, 'mean_word_length': 4.252155771905424, 'pattern_counts': {'":': 2, '<': 15, '<?xml version=': 0, '>': 340, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4719638', 'n_tokens_mistral': 28311, 'n_tokens_neox': 26959, 'n_words': 6355}