U0 JobDel(CJob *tmpc)
{//Free one cmd node.
	Free(tmpc->aux_str);
	Free(tmpc);
}

U0 JobQueueDel(CJob *head)
{
	CJob *tmpc=head->next,*tmpc1;
	while (tmpc!=head) {
		tmpc1=tmpc->next;
		QueueRemove(tmpc);
		JobDel(tmpc);
		tmpc=tmpc1;
	}
}

U0 JobCtrlInit(CJobCtrl *ctrl)
{
	QueueInit(&ctrl->next_waiting);
	QueueInit(&ctrl->next_done);
	ctrl->flags=0;
}

U0 TaskResetAwaitingMessage(CTask *task=NULL)
{//Pop-ups get parent messages so wake-up our pop-ups if we got a message.
	if (!task) task=Fs;
	PUSHFD
	CLI
	do {
		if (TaskValidate(task))
			LBtr(&task->task_flags,TASKf_AWAITING_MESSAGE);
		else
			break;
	} while (task=task->popup_task);
	POPFD
}

CJob *TaskExe(CTask *server,CTask *master,U8 *data,I64 flags)
{//Queueues a request to compile and execute src code text.
	CJob *res;
	if (!data || !TaskValidate(server) || master && !TaskValidate(master) ||
				server->popup_task && !Bt(&server->task_flags,TASKf_FILTER_INPUT))
		return NULL;
	res=ZCAlloc(sizeof(CJob));
	res->master_task=master;
	res->job_code=JOBT_EXE_STR;
	res->flags=flags;
	res->aux_str=ZStrNew(data);
	res->ctrl=&server->server_ctrl;

	PUSHFD
	CLI
	while (LBts(&server->server_ctrl.flags,JOBCf_LOCKED))
		PAUSE
	if (!TaskValidate(server)) {
		LBtr(&server->server_ctrl.flags,JOBCf_LOCKED);
		POPFD
		JobDel(res);
		return NULL;
	} else {
		LBtr(&server->task_flags,TASKf_IDLE);
		TaskResetAwaitingMessage(server);
		QueueInsert(res,server->server_ctrl.last_waiting);
		LBtr(&server->server_ctrl.flags,JOBCf_LOCKED);
		if (Bt(&flags,JOBf_WAKE_MASTER)) {
			Suspend(master);
			Yield;
		}
	}
	POPFD
	return res;
}

CJob *TaskText(CTask *server,CTask *master,U8 *data,I64 flags)
{//Post StdIn text to servant task. Tell who the master task is.
	CJob *res;
	CTask *task;
	if (!data || !TaskValidate(server) || master && !TaskValidate(master) ||
				server->popup_task && !Bt(&server->task_flags,TASKf_FILTER_INPUT))
		return NULL;
	res=ZCAlloc(sizeof(CJob));
	res->master_task=master; //in case somebody cares
	res->job_code=JOBT_TEXT_INPUT;
	res->flags=flags;
	res->aux_str=ZStrNew(data);

	PUSHFD
	task=server->last_input_filter_task;
	if (Bt(&flags,JOBf_HIGHEST_PRIORITY) || task==server) {
		if (task!=server)
			TaskWait(server);
		task=Spawn(&InputFilterTask,NULL,"Input Filter",,server);
		CLI
		task->next_input_filter_task=server->next_input_filter_task;
		task->last_input_filter_task=server;
		server->next_input_filter_task=task;
		task->next_input_filter_task->last_input_filter_task=task;
	} else {
		CLI
		task=server->next_input_filter_task;
	}
	res->ctrl=&task->server_ctrl;
	while (LBts(&task->server_ctrl.flags,JOBCf_LOCKED))
		PAUSE
	if (!TaskValidate(task)) {
		JobDel(res);
		res=NULL;
	} else {
		LBtr(&task->task_flags,TASKf_IDLE);
		TaskResetAwaitingMessage(task);
		QueueInsert(res,task->server_ctrl.last_waiting);
		LBtr(&task->server_ctrl.flags,JOBCf_LOCKED);
	}
	POPFD
	return res;
}

CJob *TaskMessage(CTask *_server,CTask *master,
				I64 message_code,I64 arg1,I64 arg2,I64 flags)
{//Post message to servant task.	Tell who the master task is.
//See $LK,"flags",A="MN:JOBf_WAKE_MASTER"$ and $LK,"message_code",A="MN:MESSAGE_CMD"$.
	CJob *tmpc1,*tmpc;
	CTask *server=_server;
	if (!TaskValidate(server) || master && !TaskValidate(master)||
				server->popup_task && !Bt(&server->task_flags,TASKf_FILTER_INPUT))
		return NULL;
	tmpc=ZCAlloc(sizeof(CJob));
	tmpc->master_task=master;
	tmpc->job_code=JOBT_MESSAGE;
	tmpc->message_code=AbsI64(message_code); //negative means do a down and up
	tmpc->aux1=arg1;
	tmpc->aux2=arg2;
	tmpc->flags=flags;
	PUSHFD
	if (Bt(&sys_semas[SEMA_RECORD_MACRO],0) &&
				server!=sys_macro_task && message_code==MESSAGE_KEY_DOWN) {
		tmpc1=ZMAllocIdent(tmpc);
		CLI
		QueueInsert(tmpc1,sys_macro_head.last);
	}
	CLI
	while (Bt(&server->task_flags,TASKf_FILTER_INPUT) &&
				!Bt(&flags,JOBf_DONT_FILTER))
		server=server->next_input_filter_task;
	tmpc->ctrl=&server->server_ctrl;
	while (LBts(&server->server_ctrl.flags,JOBCf_LOCKED))
		PAUSE
	if (!TaskValidate(server)) {
		JobDel(tmpc);
		tmpc=NULL;
	} else {
		LBtr(&server->task_flags,TASKf_IDLE);
		TaskResetAwaitingMessage(server);
		QueueInsert(tmpc,server->server_ctrl.last_waiting);
		LBtr(&server->server_ctrl.flags,JOBCf_LOCKED);
	}
	POPFD
	if (message_code<0) //Down-Up
		TaskMessage(_server,master,-message_code+1,arg1,arg2,flags);
	return tmpc;
}

Bool JobResScan(CJob *request=NULL,I64 *_res=NULL)
{//Check request complete, return with or without.
	CJobCtrl *ctrl;
	CJob *tmpc,*tmpc1;
	if (!request || Bt(&request->flags,JOBf_DONE)) {
		if (!request || request->master_task)
			ctrl=&Fs->server_ctrl;
		else
			ctrl=request->ctrl;
		PUSHFD
		CLI
		while (LBts(&ctrl->flags,JOBCf_LOCKED))
			PAUSE
		tmpc1=&ctrl->next_done;
		tmpc=tmpc1->next;
		while (tmpc!=tmpc1) {
			if (!request || request==tmpc) {
				QueueRemove(tmpc);
				LBtr(&ctrl->flags,JOBCf_LOCKED);
				POPFD
				if (_res)
					*_res=tmpc->res;
				JobDel(tmpc);
				return TRUE;
			}
			tmpc=tmpc->next;
		}
		LBtr(&ctrl->flags,JOBCf_LOCKED);
		POPFD
	}
	if (_res)
		*_res=0;
	return FALSE;
}

I64 JobResGet(CJob *request=NULL)
{//See $LK,"::/Demo/MultiCore/Lock.CC"$
	I64 res;
	CJob *tmpc1;
	if (!request) {
		tmpc1=&Fs->server_ctrl.next_done;
		while (tmpc1==tmpc1->next) {
			LBts(&Fs->task_flags,TASKf_IDLE);
			Yield;
		}
	} else {
		while (!Bt(&request->flags,JOBf_DONE)) {
			LBts(&Fs->task_flags,TASKf_IDLE);
			Yield;
		}
	}
	LBtr(&Fs->task_flags,TASKf_IDLE);
//Could get taken by someone else.
	JobResScan(request,&res);
	return res;
}

U0 TaskWait(CTask *task=NULL,Bool cmd_line_prompt=FALSE)
{//Wait for idle.
	CTask *task1;
	CJob *tmpc1;
	if (!task) task=Fs;
	if (TaskValidate(task)) {
		PUSHFD
		CLI
		while (TRUE) {
			task1=task->last_input_filter_task;
			tmpc1=&task1->server_ctrl.next_waiting;
			if (task1==Fs || !TaskValidate(task1) ||
						tmpc1==tmpc1->next && Bt(&task1->task_flags,TASKf_IDLE) &&
						(!cmd_line_prompt || Bt(&task1->task_flags,TASKf_CMD_LINE_PROMPT)))
				break;
			Yield;
		}
		POPFD
	}
}

U0 MessagePost(CTask *task,I64 message_code,I64 arg1,I64 arg2,I64 flags=0)
{//Post message to a task and return immediately.  See $LK,"message_code",A="MN:MESSAGE_CMD"$.
	if (TaskValidate(task)) {
		if (Bt(&task->task_flags,TASKf_INPUT_FILTER_TASK))
			TaskMessage(task->last_input_filter_task,NULL,message_code,arg1,arg2,
						flags|1<<JOBf_DONT_FILTER);
		else
			TaskMessage(task,NULL,message_code,arg1,arg2,flags);
	}
}

U0 MessagePostWait(CTask *task,I64 message_code,I64 arg1,I64 arg2,I64 flags=0)
{//Post message to a task and wait until task is idle.See $LK,"message_code",A="MN:MESSAGE_CMD"$.
	MessagePost(task,message_code,arg1,arg2,flags);
	TaskWait(task);
}

U0 Message(I64 message_code,I64 arg1,I64 arg2,I64 flags=0)
{//Post message to current task and return immediately. See $LK,"message_code",A="MN:MESSAGE_CMD"$.
	MessagePost(Fs,message_code,arg1,arg2,flags);
}

#define JOB_DONE				0
#define JOB_CONT				1
#define JOB_EXIT				2

I64 JobRunOne(I64 run_flags,CJobCtrl *ctrl)
{//Called with ctrl->flags,JOBCf_LOCKED.
	CJob *tmpc=ctrl->next_waiting;
	CTask 	*master;
	I64 res,flags=tmpc->flags,old_flags=RFlagsGet;
	if (Bt(&flags,JOBf_EXIT_ON_COMPLETE))
		res=JOB_EXIT;
	else
		res=JOB_CONT;
	switch (tmpc->job_code) {
		case JOBT_SPAWN_TASK:
			QueueRemove(tmpc);
			LBts(&tmpc->flags,JOBf_DISPATCHED);
			LBtr(&ctrl->flags,JOBCf_LOCKED);
			if (tmpc->aux_str)
				tmpc->spawned_task=Spawn(tmpc->addr,tmpc->fun_arg,
							tmpc->aux_str,,tmpc->aux1,tmpc->aux2,tmpc->flags);
			else
				tmpc->spawned_task=Spawn(tmpc->addr,tmpc->fun_arg,
							"Unnamed",,tmpc->aux1,tmpc->aux2,tmpc->flags);
			break;
		case JOBT_CALL:
			QueueRemove(tmpc);
			LBts(&tmpc->flags,JOBf_DISPATCHED);
			LBtr(&ctrl->flags,JOBCf_LOCKED);
			RFlagsSet(run_flags);
			LBtr(&Fs->task_flags,TASKf_IDLE);
			try
							tmpc->res=(*tmpc->addr)(tmpc->fun_arg);
			catch
				Fs->catch_except=TRUE;
			RFlagsSet(old_flags);
			break;
		case JOBT_EXE_STR:
			QueueRemove(tmpc);
			LBts(&tmpc->flags,JOBf_DISPATCHED);
			LBtr(&ctrl->flags,JOBCf_LOCKED);
			RFlagsSet(run_flags);
			LBtr(&Fs->task_flags,TASKf_IDLE);
			try
							tmpc->res=ExePrint("%s",tmpc->aux_str);
			catch
				Fs->catch_except=TRUE;
			RFlagsSet(old_flags);
			break;
		default:
			res=JOB_DONE;
	}
	if (res) {
		if (master=tmpc->master_task) {
			if (!Bt(&flags,JOBf_FREE_ON_COMPLETE)) {
				CLI
				while (LBts(&master->server_ctrl.flags,JOBCf_LOCKED))
					PAUSE
				QueueInsert(tmpc,master->server_ctrl.last_done);
				LBts(&tmpc->flags,JOBf_DONE);
				LBtr(&master->server_ctrl.flags,JOBCf_LOCKED);
				RFlagsSet(old_flags);
			}
			if (Bt(&flags,JOBf_FOCUS_MASTER) &&
						!Bt(&master->win_inhibit,WIf_SELF_FOCUS))
				sys_focus_task=master;
			if (Bt(&flags,JOBf_WAKE_MASTER))
				Suspend(master,FALSE);
		}
		if (Bt(&flags,JOBf_FREE_ON_COMPLETE))
			JobDel(tmpc);
		else if (!master) {
			CLI
			while (LBts(&ctrl->flags,JOBCf_LOCKED))
				Yield;
			QueueInsert(tmpc,ctrl->last_done);
			LBts(&tmpc->flags,JOBf_DONE);
			LBtr(&ctrl->flags,JOBCf_LOCKED);
			RFlagsSet(old_flags);
		}
	}
	return res;
}

I64 JobsHandler(I64 run_flags,CTask *task=NULL)
{//Handle all waiting cmds and return.
	I64 count=0,old_flags=RFlagsGet;
	if (!task) task=Fs;
	while (TRUE) {
		CLI
		while (LBts(&task->server_ctrl.flags,JOBCf_LOCKED))
			PAUSE
		if (task->server_ctrl.next_waiting!=&task->server_ctrl)
			switch (JobRunOne(run_flags,&task->server_ctrl)) {
				case JOB_CONT:
					count++;
					break;
				case JOB_EXIT:
					Exit;
				case JOB_DONE:
					goto jh_done;
			}
		else
			goto jh_done;
	}
jh_done:
	LBtr(&task->server_ctrl.flags,JOBCf_LOCKED);
	RFlagsSet(old_flags);
	return count;
}

I64 PopUp(U8 *buf,CTask *parent=NULL,CTask **_pu_task=NULL)
{//Execute code in $LK,"PopUp",A="MN:PopUp"$ task.
	I64 res;
	CJob *tmpc;
	CTask *task=Spawn(&ServerCmdLine,NULL,"Server",,parent);
	if (!parent) {
		TaskExe(task,parent,buf,1<<JOBf_EXIT_ON_COMPLETE|1<<JOBf_FREE_ON_COMPLETE);
		if (_pu_task) *_pu_task=task;
		return 0;
	} else {
		Fs->popup_task=task;
		tmpc=TaskExe(task,parent,buf,1<<JOBf_WAKE_MASTER|1<<JOBf_FOCUS_MASTER);
		if (_pu_task) *_pu_task=task;
		JobResScan(tmpc,&res);
		Fs->popup_task=NULL;
		Kill(task);
		if (_pu_task) *_pu_task=NULL;
		return res;
	}
}

I64 PopUpPrint(U8 *format,...)
{//Execute code in $LK,"PopUp",A="MN:PopUp"$ task.
	U8 *buf=StrPrintJoin(NULL,format,argc,argv);
	I64 res;
	res=PopUp(buf,Fs);
	Free(buf);
	return res;
}

I64 Zenith(U8 *format,...)
{//Make zenith_task execute code.
	I64 res;
	U8 *buf=StrPrintJoin(NULL,format,argc,argv);
	CJob *tmpc;
	if (Fs==zenith_task) {
		tmpc=TaskExe(zenith_task,Fs,buf,0);
		JobsHandler(RFlagsGet);
	} else {
		TaskWait(zenith_task);
		tmpc=TaskExe(zenith_task,Fs,buf,1<<JOBf_WAKE_MASTER);
	}
	JobResScan(tmpc,&res);
	Free(buf);
	return res;
}

U0 ZenithLog(U8 *format,...)
{//Display text in zenith_task.
	U8 *buf=StrPrintJoin(NULL,format,argc,argv);
	if (Fs==zenith_task)
		"%s",buf;
	else if (!IsSingleUser)
		Zenith("\"%%s\",%d;",buf);
	Free(buf);
}

U0 ZenithWarn(U8 *format,...)
{//Display pink blinking Warn text in zenith_task.
	U8 *buf=StrPrintJoin(NULL,format,argc,argv),
				*st=MStrPrint(ST_WARN_ST "%s",buf);
	if (Fs==zenith_task)
		"%s",st;
	else if (!IsSingleUser)
		Zenith("\"%%s\",%d;",st);
	Free(st);
	Free(buf);
}

U0 ZenithErr(U8 *format,...)
{//Display red blinking Err text in zenith_task.
	U8 *buf=StrPrintJoin(NULL,format,argc,argv),
				*st=MStrPrint(ST_ERR_ST "%s",buf);
	if (Fs==zenith_task)
		"%s",st;
	else if (!IsSingleUser)
		Zenith("\"%%s\",%d;",st);
	Free(st);
	Free(buf);
}

U0 XTalk(CTask *task,U8 *format,...)
{//Sends text to other task. See $LK,"::/Misc/OSTestSuite.CC"$.
	U8 *buf=StrPrintJoin(NULL,format,argc,argv),*st=ZStrNew(buf),
				*st2=MStrPrint("\"%%s\",%d;Free(%d);",st,st);
	TaskText(task,NULL,st2,0);
	Free(st2);
	Free(buf);
}

U0 XTalkWait(CTask *task,U8 *format,...)
{//Send text to other task and wait for it to idle.
	U8 *buf=StrPrintJoin(NULL,format,argc,argv),*st=ZStrNew(buf),
				*st2=MStrPrint("\"%%s\",%d;Free(%d);",st,st);
	TaskText(task,NULL,st2,0);
	Free(st2);
	Free(buf);
	TaskWait(task);
}

U0 InStr(U8 *format,...)
{//Send InFile code to self.
	U8 *buf=StrPrintJoin(NULL,format,argc,argv);
	if (Bt(&Fs->task_flags,TASKf_INPUT_FILTER_TASK))
		ExePrint("%s",buf);
	else
		TaskText(Fs,NULL,buf,1<<JOBf_HIGHEST_PRIORITY);
	Free(buf);
}

U0 InFile(U8 *filename)
{//Send InFile code file to self.
	U8 *name=ExtDefault(filename,"IN");
	InStr("Cd(\"%C:%s\");;#include \"%s\"",
				Drive2Letter(Fs->cur_dv),Fs->cur_dir,name);
	Free(name);
}

U0 In(U8 *format,...)
{//Send text to own input buffer. See $LK,"::/Demo/AcctExample/TOS/TOSDistro.CC"$.
	U8 *buf=StrPrintJoin(NULL,format,argc,argv),*st=ZStrNew(buf);
	InStr("\"%%s\",%d;Free(%d);",st,st);
	Free(buf);
}

U0 XTalkStr(CTask *task,U8 *format,...)
{//Send InFile code to other task.
	U8 *buf=StrPrintJoin(NULL,format,argc,argv);
	TaskText(task,NULL,buf,0);
	Free(buf);
}

U0 XTalkStrWait(CTask *task,U8 *format,...)
{//Send InFile code to other task and wait for it to idle.
	U8 *buf=StrPrintJoin(NULL,format,argc,argv);
	TaskText(task,NULL,buf,0);
	Free(buf);
	TaskWait(task);
}