<commit_before>var app = null;
var g_Plugins = {};

function msg(msg)
{
	var d = $("#dbg");

	d.append(msg + '\n');
	d.scrollTop(d[0].scrollHeight - d.height());
}

function make(tag)
{
	return $(document.createElement(tag));
}

function make_menu_plugin(id)
{
	var li = make('li');
	
	li.addClass('menu_item');
	
	var a = make('a');
	
	li.append(a);
	a.attr('href', '#' + id);
	a.text(id);
	
	return li;
}

function PluginManager(core, base_url) {
	this.base_url = base_url;
	this.core = core;

	var self = this;
	var plugs = [ "test_float_generator",
		      "test_string_generator",
		      "test_interval_generator",
		      "test_modulator",
		      "test_emitter" ]; // TODO: Get this from a JSON file (or something) in the root of the plugins dir.
	
	var menu = make('ul');
	
	for(var i = 0; i < plugs.length; i++) {
		var id = plugs[i];
		
		// Load the plugin, constrain filenames.
		url = self.base_url + "/" + id + ".plugin.js";
		
		$.getScript(url, (function(id) { return function(data, status) {
			if(status == "success")
			{	
				menu.append(make_menu_plugin(id))
				msg("Loaded " + id);
			}
			else
				msg("Failed to load plugin '" + id + "'");
		}})(id));  	
	}
	
	$('#context_menu').append(menu);
	$('#canvas').contextMenu({ menu: "context_menu" }, app.onPluginInstantiated);
	this.create = function(id) {
		return new g_Plugins[id](self.core);
	}
}
 
function ConnectionUI(parent_conn)
{
	this.src_pos = [0, 0];
	this.dst_pos = [0, 0];
	this.src_slot_div = null;
	this.dst_slot_div = null;
	this.color = '#000';
	this.parent_conn = parent_conn;
	this.offset = 0;
}

function Connection(src_node, dst_node, src_slot, dst_slot)
{
	this.src_node = src_node;
	this.dst_node = dst_node;
	this.src_slot = src_slot;
	this.dst_slot = dst_slot;
	this.ui = null;
	
	var self = this;
	
	this.create_ui = function()
	{
		self.ui = new ConnectionUI(self);
	};
	
	this.destroy_ui = function()
	{
		if(self.ui)
			self.ui = null;
	};
}

function NodeUI(parent_node, x, y) {
	this.parent_node = parent_node;
	this.x = x;
	this.y = y;
	
	var render_slots = function(nid, col, slots, type)
	{
		for(var i = 0; i < slots.length; i++)
		{
			var s = slots[i];
			var div = make('div');
			
			div.attr('id', nid + (type === 0 ? 'si' : 'so') + i);
			div.text(s.name);
			div.addClass('pl_slot');
			div.disableSelection();
			div.definition = s;
			
			div.mouseenter(app.onSlotEntered(parent_node, s, div));
			div.mouseleave(app.onSlotExited(parent_node, s, div));

			if(type === 1)
				div.mousedown(app.onSlotClicked(parent_node, s, div));
			
			col.append(div);
		}
	};
	
	var nid = 'n' + parent_node.uid;
	
	this.dom = make('div');
	this.dom.addClass('plugin');
	this.dom.addClass('ui-widget-content')
	this.dom.attr('id', nid);
	this.dom.mousemove(app.onMouseMoved); // Make sure we don't stall during slot connection, when the mouse enters a node.
	
	this.dom.css('top', '' + y + 'px');
	this.dom.css('left', '' + x + 'px');
	
	var table = make('table');
	
	table.addClass('pl_layout');
	this.dom.append(table);
	
	var h_row = make('tr');
	var h_cell = make('td');
	
	h_cell.text(parent_node.id);
	h_cell.attr('colspan', '3');
	h_cell.disableSelection();
	h_row.append(h_cell);
	h_row.addClass('pl_header');
	h_row.click(app.onNodeHeaderClicked);
	h_row.mouseenter(app.onNodeHeaderEntered(parent_node));
	h_row.mouseleave(app.onNodeHeaderExited);
	table.append(h_row);
	
	this.header_row = h_row;
	
	var row = make('tr');
	
	table.append(row)
	
	var input_col = make('td');
	var content_col = make('td');
	var output_col = make('td');
	
	input_col.css('text-align', 'left');
	content_col.css('width', '100%');
	output_col.css('text-align', 'right');
	
	row.append(input_col)
	row.append(content_col)
	row.append(output_col)
	
	render_slots(nid, input_col, parent_node.plugin.input_slots, 0);
	render_slots(nid, output_col, parent_node.plugin.output_slots, 1);
	
	content_col.append(parent_node.plugin.create_ui());
	
	this.dom.draggable({containment:'parent', drag: app.onNodeDragged(parent_node) });
	
	$("#canvas_parent").append(this.dom)
}

function Node(parent_graph, plugin_id, x, y) {
	var self = this;
	
	this.parent_graph = parent_graph;
	this.plugin = app.core.plugin_mgr.create(plugin_id);
	this.x = x;
	this.y = y;
	this.ui = null;
	this.id = plugin_id;
	this.uid = parent_graph.get_node_uid();
	this.rendering_state = 0;
	
	// Decorate the slots with their index to make this immediately resolvable
	// from a slot reference, allowing for faster code elsewhere.
	// Additionally tagged with the type (0 = input, 1 = output) for similar reasons.
	
	for(var i = 0; i < this.plugin.input_slots.length; i++)
	{
		var s = this.plugin.input_slots[i];
		
		s.index = i;
		s.type = 0;
	}
		
	for(var i = 0; i < this.plugin.output_slots.length; i++)
	{
		var s = this.plugin.output_slots[i];
		
		s.index = i;
		s.type = 1;
	}
	
	this.create_ui = function()
	{
		self.ui = new NodeUI(self, self.x, self.y);
	};
	
	this.destroy_ui = function()
	{
		if(self.ui)
			self.ui.dom.remove();
		
		self.ui = null;
	};
	
	this.destroy = function()
	{
		var graph = self.parent_graph;
		var index = graph.nodes.indexOf(self);
		
		if(index != -1)
			graph.nodes.splice(index, 1);
		
		var pending = [];
		var conns = graph.connections;
		
		for(var i = 0; i < conns.length; i++)
		{
			var c = conns[i];
			
			if(c.src_node.uid === self.uid || c.dst_node.uid === self.uid)
				pending.push(c);
		}
		
		for(var i = 0; i < pending.length; i++)
			graph.destroy_connection(pending[i]);
		
		self.destroy_ui();
	}
	
	this.update_recursive = function(conns, delta_t)
	{
		if(self.rendering_state == 1)
			return;
		
		var uid = self.uid;
		var inputs = self.inputs = [];
		
		for(var i = 0; i < conns.length; i++)
		{
			var c = conns[i];
			
			if(c.dst_node.uid == uid)
				inputs.push(c);
		}
		
		for(var i = 0; i < inputs.length; i++)
		{
			var i = inputs[i];
			
			i.src_node.update_recursive(conns, delta_t);
			
			var value = i.src_node.plugin.update_output(i.src_slot.index);
			
			self.plugin.update_input(i.dst_slot.index, value);
		}
		
		if(self.plugin.update_state)
			self.plugin.update_state(delta_t);
	
		if(self.ui)
			self.ui.dom.css('background-color', '#0f0');
		
		self.rendering_state = 1;
	}
}


function Graph(parent_graph) {
	var self = this;
	
	this.parent_graph = parent_graph;
	this.nodes = [];
	this.connections = [];
	this.node_uid = 0;

	this.get_node_uid = function()
	{
		return self.node_uid++;
	};
	
	this.create_instance = function(plugin_id, x, y)
	{
		n = new Node(self, plugin_id, x, y);
		
		self.nodes.push(n);
		return n;
	};
	
	this.update = function(delta_t)
	{
		var nodes = self.nodes;
		var roots = [];
		
		for(var i = 0; i < nodes.length; i++)
		{
			var node = nodes[i];
			
			if(node.plugin.output_slots.length == 0)
				roots.push(node);
				
			node.rendering_state = 0;
		}
		
		for(var i = 0; i < roots.length; i++)
		{
			var root = roots[i];
			
			root.update_recursive(self.connections, delta_t);
		}
	};
	
	this.destroy_connection = function(c)
	{
		var index = self.connections.indexOf(c);
		
		if(index != -1)
		{
			self.connections.splice(index, 1);
			msg('Destroying connection = ' + index);
		}
		
		c.src_slot.is_connected = false;
		c.dst_slot.is_connected = false;
	};
	
	this.find_connection_to = function(node, slot)
	{
		if(slot.type !== 0)
			return null;
		
		var conns = self.connections;
		var uid = node.uid;
		
		for(var i = 0; i < conns.length; i++)
		{
			var c = conns[i];
			
			if(c.dst_node.uid = uid && c.dst_slot == slot)
				return c;			
		}
		
		return null;
	};
	
	this.find_connections_from = function(node, slot)
	{
		if(slot.type !== 1)
			return [];
		
		var conns = self.connections;
		var uid = node.uid;
		var result = [];
		
		msg('search ' + conns.length);
		
		for(var i = 0; i < conns.length; i++)
		{
			var c = conns[i];
			
			msg('uid = ' + uid + ', src_uid = ' + c.src_node.uid);
			if(c.src_node.uid === uid && c.src_slot === slot)
			{
				msg('found match');
				result.push(c);			
			}
		}
		
		return result;
	};
}

function Core() {
	var self = this;
	
	this.datatypes = {
		FLOAT: { id: 0, name: 'Float' },
		STRING: { id: 1, name: 'String' },
		TEXTURE: { id: 2, name: 'Texture' }
	};
	
	this.active_graph = this.root_graph = new Graph(null);
	
	this.update = function(delta_t)
	{
		self.active_graph.update(delta_t);
	}
}

function Application() {
	var canvas = $("#canvas");
		
	this.state = {
		STOPPED: 0,
		PLAYING: 1,<commit_msg>Removed spurious (and now obsolete) debugging spew.<commit_after>var app = null;
var g_Plugins = {};

function msg(msg)
{
	var d = $("#dbg");

	d.append(msg + '\n');
	d.scrollTop(d[0].scrollHeight - d.height());
}

function make(tag)
{
	return $(document.createElement(tag));
}

function make_menu_plugin(id)
{
	var li = make('li');
	
	li.addClass('menu_item');
	
	var a = make('a');
	
	li.append(a);
	a.attr('href', '#' + id);
	a.text(id);
	
	return li;
}

function PluginManager(core, base_url) {
	this.base_url = base_url;
	this.core = core;

	var self = this;
	var plugs = [ "test_float_generator",
		      "test_string_generator",
		      "test_interval_generator",
		      "test_modulator",
		      "test_emitter" ]; // TODO: Get this from a JSON file (or something) in the root of the plugins dir.
	
	var menu = make('ul');
	
	for(var i = 0; i < plugs.length; i++) {
		var id = plugs[i];
		
		// Load the plugin, constrain filenames.
		url = self.base_url + "/" + id + ".plugin.js";
		
		$.getScript(url, (function(id) { return function(data, status) {
			if(status == "success")
			{	
				menu.append(make_menu_plugin(id))
				msg("Loaded " + id);
			}
			else
				msg("Failed to load plugin '" + id + "'");
		}})(id));  	
	}
	
	$('#context_menu').append(menu);
	$('#canvas').contextMenu({ menu: "context_menu" }, app.onPluginInstantiated);
	this.create = function(id) {
		return new g_Plugins[id](self.core);
	}
}
 
function ConnectionUI(parent_conn)
{
	this.src_pos = [0, 0];
	this.dst_pos = [0, 0];
	this.src_slot_div = null;
	this.dst_slot_div = null;
	this.color = '#000';
	this.parent_conn = parent_conn;
	this.offset = 0;
}

function Connection(src_node, dst_node, src_slot, dst_slot)
{
	this.src_node = src_node;
	this.dst_node = dst_node;
	this.src_slot = src_slot;
	this.dst_slot = dst_slot;
	this.ui = null;
	
	var self = this;
	
	this.create_ui = function()
	{
		self.ui = new ConnectionUI(self);
	};
	
	this.destroy_ui = function()
	{
		if(self.ui)
			self.ui = null;
	};
}

function NodeUI(parent_node, x, y) {
	this.parent_node = parent_node;
	this.x = x;
	this.y = y;
	
	var render_slots = function(nid, col, slots, type)
	{
		for(var i = 0; i < slots.length; i++)
		{
			var s = slots[i];
			var div = make('div');
			
			div.attr('id', nid + (type === 0 ? 'si' : 'so') + i);
			div.text(s.name);
			div.addClass('pl_slot');
			div.disableSelection();
			div.definition = s;
			
			div.mouseenter(app.onSlotEntered(parent_node, s, div));
			div.mouseleave(app.onSlotExited(parent_node, s, div));

			if(type === 1)
				div.mousedown(app.onSlotClicked(parent_node, s, div));
			
			col.append(div);
		}
	};
	
	var nid = 'n' + parent_node.uid;
	
	this.dom = make('div');
	this.dom.addClass('plugin');
	this.dom.addClass('ui-widget-content')
	this.dom.attr('id', nid);
	this.dom.mousemove(app.onMouseMoved); // Make sure we don't stall during slot connection, when the mouse enters a node.
	
	this.dom.css('top', '' + y + 'px');
	this.dom.css('left', '' + x + 'px');
	
	var table = make('table');
	
	table.addClass('pl_layout');
	this.dom.append(table);
	
	var h_row = make('tr');
	var h_cell = make('td');
	
	h_cell.text(parent_node.id);
	h_cell.attr('colspan', '3');
	h_cell.disableSelection();
	h_row.append(h_cell);
	h_row.addClass('pl_header');
	h_row.click(app.onNodeHeaderClicked);
	h_row.mouseenter(app.onNodeHeaderEntered(parent_node));
	h_row.mouseleave(app.onNodeHeaderExited);
	table.append(h_row);
	
	this.header_row = h_row;
	
	var row = make('tr');
	
	table.append(row)
	
	var input_col = make('td');
	var content_col = make('td');
	var output_col = make('td');
	
	input_col.css('text-align', 'left');
	content_col.css('width', '100%');
	output_col.css('text-align', 'right');
	
	row.append(input_col)
	row.append(content_col)
	row.append(output_col)
	
	render_slots(nid, input_col, parent_node.plugin.input_slots, 0);
	render_slots(nid, output_col, parent_node.plugin.output_slots, 1);
	
	content_col.append(parent_node.plugin.create_ui());
	
	this.dom.draggable({containment:'parent', drag: app.onNodeDragged(parent_node) });
	
	$("#canvas_parent").append(this.dom)
}

function Node(parent_graph, plugin_id, x, y) {
	var self = this;
	
	this.parent_graph = parent_graph;
	this.plugin = app.core.plugin_mgr.create(plugin_id);
	this.x = x;
	this.y = y;
	this.ui = null;
	this.id = plugin_id;
	this.uid = parent_graph.get_node_uid();
	this.rendering_state = 0;
	
	// Decorate the slots with their index to make this immediately resolvable
	// from a slot reference, allowing for faster code elsewhere.
	// Additionally tagged with the type (0 = input, 1 = output) for similar reasons.
	
	for(var i = 0; i < this.plugin.input_slots.length; i++)
	{
		var s = this.plugin.input_slots[i];
		
		s.index = i;
		s.type = 0;
	}
		
	for(var i = 0; i < this.plugin.output_slots.length; i++)
	{
		var s = this.plugin.output_slots[i];
		
		s.index = i;
		s.type = 1;
	}
	
	this.create_ui = function()
	{
		self.ui = new NodeUI(self, self.x, self.y);
	};
	
	this.destroy_ui = function()
	{
		if(self.ui)
			self.ui.dom.remove();
		
		self.ui = null;
	};
	
	this.destroy = function()
	{
		var graph = self.parent_graph;
		var index = graph.nodes.indexOf(self);
		
		if(index != -1)
			graph.nodes.splice(index, 1);
		
		var pending = [];
		var conns = graph.connections;
		
		for(var i = 0; i < conns.length; i++)
		{
			var c = conns[i];
			
			if(c.src_node.uid === self.uid || c.dst_node.uid === self.uid)
				pending.push(c);
		}
		
		for(var i = 0; i < pending.length; i++)
			graph.destroy_connection(pending[i]);
		
		self.destroy_ui();
	}
	
	this.update_recursive = function(conns, delta_t)
	{
		if(self.rendering_state == 1)
			return;
		
		var uid = self.uid;
		var inputs = self.inputs = [];
		
		for(var i = 0; i < conns.length; i++)
		{
			var c = conns[i];
			
			if(c.dst_node.uid == uid)
				inputs.push(c);
		}
		
		for(var i = 0; i < inputs.length; i++)
		{
			var i = inputs[i];
			
			i.src_node.update_recursive(conns, delta_t);
			
			var value = i.src_node.plugin.update_output(i.src_slot.index);
			
			self.plugin.update_input(i.dst_slot.index, value);
		}
		
		if(self.plugin.update_state)
			self.plugin.update_state(delta_t);
	
		if(self.ui)
			self.ui.dom.css('background-color', '#0f0');
		
		self.rendering_state = 1;
	}
}


function Graph(parent_graph) {
	var self = this;
	
	this.parent_graph = parent_graph;
	this.nodes = [];
	this.connections = [];
	this.node_uid = 0;

	this.get_node_uid = function()
	{
		return self.node_uid++;
	};
	
	this.create_instance = function(plugin_id, x, y)
	{
		n = new Node(self, plugin_id, x, y);
		
		self.nodes.push(n);
		return n;
	};
	
	this.update = function(delta_t)
	{
		var nodes = self.nodes;
		var roots = [];
		
		for(var i = 0; i < nodes.length; i++)
		{
			var node = nodes[i];
			
			if(node.plugin.output_slots.length == 0)
				roots.push(node);
				
			node.rendering_state = 0;
		}
		
		for(var i = 0; i < roots.length; i++)
		{
			var root = roots[i];
			
			root.update_recursive(self.connections, delta_t);
		}
	};
	
	this.destroy_connection = function(c)
	{
		var index = self.connections.indexOf(c);
		
		if(index != -1)
			self.connections.splice(index, 1);
		
		c.src_slot.is_connected = false;
		c.dst_slot.is_connected = false;
	};
	
	this.find_connection_to = function(node, slot)
	{
		if(slot.type !== 0)
			return null;
		
		var conns = self.connections;
		var uid = node.uid;
		
		for(var i = 0; i < conns.length; i++)
		{
			var c = conns[i];
			
			if(c.dst_node.uid = uid && c.dst_slot == slot)
				return c;			
		}
		
		return null;
	};
	
	this.find_connections_from = function(node, slot)
	{
		if(slot.type !== 1)
			return [];
		
		var conns = self.connections;
		var uid = node.uid;
		var result = [];
		
		for(var i = 0; i < conns.length; i++)
		{
			var c = conns[i];
			
			if(c.src_node.uid === uid && c.src_slot === slot)
				result.push(c);			
		}
		
		return result;
	};
}

function Core() {
	var self = this;
	
	this.datatypes = {
		FLOAT: { id: 0, name: 'Float' },
		STRING: { id: 1, name: 'String' },
		TEXTURE: { id: 2, name: 'Texture' }
	};
	
	this.active_graph = this.root_graph = new Graph(null);
	
	this.update = function(delta_t)
	{
		self.active_graph.update(delta_t);
	}
}

function Application() {
	var canvas = $("#canvas");
		
	this.state = {
		STOPPED: 0,
		PLAYING: 1,
{'fraction_non_alphanumeric': 0.1694682880305198, 'fraction_numerical': 0.00619933237958989, 'mean_word_length': 3.2026553106212425, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4353316', 'n_tokens_mistral': 7436, 'n_tokens_neox': 6453, 'n_words': 1322}