-
在Unity中创建基于Node节点的编辑器 (二) 窗口序列化
2018-05-13 17:30:44孙广东 2018.5.13第二部分 在Unity中序列化基于节点的编辑器重温基于节点的编辑器 上一篇文章 《在Unity中创建基于Node节点的编辑器 (一)》重温XML序列化 有许多序列化选项,例如JSON或Unity自己的序列化系统。...孙广东 2018.5.13
csdn 的产品 , 真垃圾, 不想吐槽了, 文章保存就丢! 没办法 。 怎么不满意, 还是得继续用, 哎~~~
第二部分 在Unity中序列化基于节点的编辑器重温基于节点的编辑器上一篇文章 《在Unity中创建基于Node节点的编辑器 (一)
》重温XML序列化有许多序列化选项,例如JSON或Unity自己的序列化系统。 我们以前已经介绍过XML序列化 我们将使用XMLOp 类和本文中的一些XML属性。添加一个菜单栏(添加保存, 加载等按钮)我们将从添加菜单栏开始,然后从控制台窗口clone中复制它(一起弄的一个 仿Unity Console窗口的脚本):private float menuBarHeight = 20f;private Rect menuBar;private void OnGUI(){DrawGrid(20, 0.2f, Color.gray);DrawGrid(100, 0.4f, Color.gray);DrawMenuBar();DrawNodes();DrawConnections();DrawConnectionLine(Event.current);ProcessNodeEvents(Event.current);ProcessEvents(Event.current);if (GUI.changed) Repaint();}private void DrawMenuBar(){menuBar = new Rect(0, 0, position.width, menuBarHeight);GUILayout.BeginArea(menuBar, EditorStyles.toolbar);GUILayout.BeginHorizontal();GUILayout.Button(new GUIContent("Save"), EditorStyles.toolbarButton, GUILayout.Width(35));GUILayout.Space(5);GUILayout.Button(new GUIContent("Load"), EditorStyles.toolbarButton, GUILayout.Width(35));GUILayout.EndHorizontal();GUILayout.EndArea();}在第56行,我们调用DrawMenuBar()方法,并在第69-82行之间创建菜单栏。控制台窗口克隆有一个button和6个toggles,但由于我们只是序列化和反序列化,所以我们不需要两个以上的按钮。请记住,编辑器GUI系统具有绘制顺序,并按照您调用它们的顺序从后向前绘制元素。这就是我们绘制网格后绘制菜单栏的原因。否则,网格将被拖到菜单栏上。DrawMenuBar() 在 DrawGrid() 之前被调用,因此在菜单栏上有一个丑陋的网格。目前,保存和加载按钮什么都不做,但我们会做到。序列化接下来,我们需要准备我们的类(节点Node和连接Connection)进行序列化。让我们记住关于XML序列化的两个重要关键点:- XML序列化程序只能序列化public 字段。
- 要序列化的类应该有一个无参数的构造函数。
规则1号不会导致很多问题(它仍然会导致一些问题,但我们会做到这一点),但规则编号2是有问题的。我们的两个类都有带参数的构造函数。首先解决这个问题:public Node() { }public Node(Vector2 position, float width, float height, GUIStyle nodeStyle, GUIStyle selectedStyle, GUIStyle inPointStyle, GUIStyle outPointStyle, Action<ConnectionPoint> OnClickInPoint, Action<ConnectionPoint> OnClickOutPoint, Action<Node> OnClickRemoveNode){rect = new Rect(position.x, position.y, width, height);style = nodeStyle;inPoint = new ConnectionPoint(this, ConnectionPointType.In, inPointStyle, OnClickInPoint);outPoint = new ConnectionPoint(this, ConnectionPointType.Out, outPointStyle, OnClickOutPoint);defaultNodeStyle = nodeStyle;selectedNodeStyle = selectedStyle;OnRemoveNode = OnClickRemoveNode;}public Connection() { }public Connection(ConnectionPoint inPoint, ConnectionPoint outPoint, Action<Connection> OnClickRemoveConnection){this.inPoint = inPoint;this.outPoint = outPoint;this.OnClickRemoveConnection = OnClickRemoveConnection;}接下来,我们将忽略无法序列化或不需要序列化的属性。 例如,在Node 类中,GUIStyles 可以不用序列化,因为它们已经由编辑器自己提供。 我们不需要isDragged或isSelected。 实际上,Node类只有一个需要序列化的属性:rect。 让我们来看看如何正确地忽略不必要和不可序列化的属性后Node类的样子:public class Node{public Rect rect;[XmlIgnore] public string title;[XmlIgnore] public bool isDragged;[XmlIgnore] public bool isSelected;[XmlIgnore] public ConnectionPoint inPoint;[XmlIgnore] public ConnectionPoint outPoint;[XmlIgnore] public GUIStyle style;[XmlIgnore] public GUIStyle defaultNodeStyle;[XmlIgnore] public GUIStyle selectedNodeStyle;[XmlIgnore] public Action<Node> OnRemoveNode;public Node(){}信不信由你,Node类已准备好在这一点上被序列化。 所以,我们来序列化节点!保存节点记得保存按钮什么也没做? 那么,它至少应该保存节点,因为它们现在是可序列化的。 节点保存的方法非常简单:private void Save(){XMLOp.Serialize(nodes, "Assets/Resources/nodes.xml");}我们将在用户点击保存按钮时调用该方法:private void DrawMenuBar(){menuBar = new Rect(0, 0, position.width, menuBarHeight);GUILayout.BeginArea(menuBar, EditorStyles.toolbar);GUILayout.BeginHorizontal();if (GUILayout.Button(new GUIContent("Save"), EditorStyles.toolbarButton, GUILayout.Width(35))){Save();}GUILayout.Space(5);GUILayout.Button(new GUIContent("Load"), EditorStyles.toolbarButton, GUILayout.Width(35));GUILayout.EndHorizontal();GUILayout.EndArea();}现在,打开基于节点的编辑器,放置几个节点,然后点击保存(在此之前,您必须在资源下有一个Resources 文件夹)。 Unity会在Resources 中创建一个名为nodes.xml的文件,如果您看不到它,只需右键单击资源,然后单击重新导入 Ctrl + R。 nodes.xml文件的内容应该是这样的:<?xml version="1.0" encoding="us-ascii"?><ArrayOfNode xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Node><rect><x>166</x><y>183</y><position><x>166</x><y>183</y></position><center><x>266</x><y>208</y></center><min><x>166</x><y>183</y></min><max><x>366</x><y>233</y></max><width>200</width><height>50</height><size><x>200</x><y>50</y></size><xMin>166</xMin><yMin>183</yMin><xMax>366</xMax><yMax>233</yMax></rect></Node><Node><rect><x>345</x><y>345</y><position><x>345</x><y>345</y></position><center><x>445</x><y>370</y></center><min><x>345</x><y>345</y></min><max><x>545</x><y>395</y></max><width>200</width><height>50</height><size><x>200</x><y>50</y></size><xMin>345</xMin><yMin>345</yMin><xMax>545</xMax><yMax>395</yMax></rect></Node></ArrayOfNode>所以,现在我们的节点可以被序列化,如果我们可以序列化Connections,我们的节点编辑器将是完全可序列化的。 那么我们继续吧。序列化连接Connection类只有两个可以序列化的属性:inPoint (ConnectionPoint)和outPoint (ConnectionPoint).。 但是,序列化这两个属性将毫无意义,因为对象在反序列化后不会保留对其他对象的引用。 这意味着,如果我们反序列化一个连接,它会创建两个连接点并连接它们,但这些连接点不属于它用来连接的节点(参见下图)。序列化破坏连接,反序列化不能解决这个问题。为了解决这个问题,我们需要某种连接点的标识符,即一个唯一的ID,这样在反序列化后,我们可以通过它们的ID来查找这些连接点,并为实际对象提供参考以恢复连接。public class ConnectionPoint{public string id;[XmlIgnore] public Rect rect;[XmlIgnore] public ConnectionPointType type;[XmlIgnore] public Node node;[XmlIgnore] public GUIStyle style;[XmlIgnore] public Action<ConnectionPoint> OnClickConnectionPoint;public ConnectionPoint() { }public ConnectionPoint(Node node, ConnectionPointType type, GUIStyle style, Action<ConnectionPoint> OnClickConnectionPoint, string id = null){this.node = node;this.type = type;this.style = style;this.OnClickConnectionPoint = OnClickConnectionPoint;rect = new Rect(0, 0, 10f, 20f);this.id = id ?? Guid.NewGuid().ToString();}public class Connection{public ConnectionPoint inPoint;public ConnectionPoint outPoint;[XmlIgnore] public Action<Connection> OnClickRemoveConnection;public class Node{public Rect rect;[XmlIgnore] public string title;[XmlIgnore] public bool isDragged;[XmlIgnore] public bool isSelected;public ConnectionPoint inPoint;public ConnectionPoint outPoint;[XmlIgnore] public GUIStyle style;[XmlIgnore] public GUIStyle defaultNodeStyle;[XmlIgnore] public GUIStyle selectedNodeStyle;[XmlIgnore] public Action<Node> OnRemoveNode;当然,我们需要更新我们的Save方法以包含连接:private void Save(){XMLOp.Serialize(nodes, "Assets/Resources/nodes.xml");XMLOp.Serialize(connections, "Assets/Resources/connections.xml");}这就结束了序列化(坦率地说,很难)的一部分。 现在我们有一个基于节点的编辑器的当前状态的XML表示。 我们所要做的就是将其转换回来。反序列化首先要做的是:Load按钮应该是功能性的。private void DrawMenuBar(){menuBar = new Rect(0, 0, position.width, menuBarHeight);GUILayout.BeginArea(menuBar, EditorStyles.toolbar);GUILayout.BeginHorizontal();if (GUILayout.Button(new GUIContent("Save"), EditorStyles.toolbarButton, GUILayout.Width(35))){Save();}GUILayout.Space(5);if (GUILayout.Button(new GUIContent("Load"), EditorStyles.toolbarButton, GUILayout.Width(35))){Load();}GUILayout.EndHorizontal();GUILayout.EndArea();}我们将在 Load() 方法中反序列化 XML 文件的内容,创建节点和连接并将它们分配给它们各自的属性。 反序列化XML文件是一个非常简单的过程; 我们所要做的就是调用XMLOp.Deserialize<T>(string):private void Load(){var nodesDeserialized = XMLOp.Deserialize<List<Node>>("Assets/Resources/nodes.xml");var connectionsDeserialized = XMLOp.Deserialize<List<Connection>>("Assets/Resources/connections.xml");nodes = new List<Node>();connections = new List<Connection>();}然而,单独对XML文件进行反序列化并不足以将编辑器恢复到最后一个状态,因为如上图所示,我们在序列化时打破了节点和连接之间的关系,我们需要重新连接它们。 此重新连接过程需要通过ID查找节点并在它们之间创建连接。 这就是为什么我们为ConnectionPoint类添加了唯一ID。 我们需要用这些ID重新创建ConnectionPoints,所以我们要添加另一个构造函数给Node类:public Node(Vector2 position, float width, float height, GUIStyle nodeStyle, GUIStyle selectedStyle, GUIStyle inPointStyle, GUIStyle outPointStyle, Action<ConnectionPoint> OnClickInPoint,Action<ConnectionPoint> OnClickOutPoint, Action<Node> OnClickRemoveNode, string inPointID, string outPointID){rect = new Rect(position.x, position.y, width, height);style = nodeStyle;inPoint = new ConnectionPoint(this, ConnectionPointType.In, inPointStyle, OnClickInPoint, inPointID);outPoint = new ConnectionPoint(this, ConnectionPointType.Out, outPointStyle, OnClickOutPoint, outPointID);defaultNodeStyle = nodeStyle;selectedNodeStyle = selectedStyle;OnRemoveNode = OnClickRemoveNode;}这是一个新的构造函数,它将创建一个具有两个给定(而不是生成)ID的ConnectionPoint的Node。 现在我们将基于反序列化节点创建新节点:private void Load(){var nodesDeserialized = XMLOp.Deserialize<List<Node>>("Assets/Resources/nodes.xml");var connectionsDeserialized = XMLOp.Deserialize<List<Connection>>("Assets/Resources/connections.xml");nodes = new List<Node>();connections = new List<Connection>();foreach (var nodeDeserialized in nodesDeserialized){nodes.Add(new Node(nodeDeserialized.rect.position,nodeDeserialized.rect.width,nodeDeserialized.rect.height,nodeStyle,selectedNodeStyle,inPointStyle,outPointStyle,OnClickInPoint,OnClickOutPoint,OnClickRemoveNode,nodeDeserialized.inPoint.id,nodeDeserialized.outPoint.id));}}继续尝试。 创建几个节点,保存它,关闭编辑器,再次打开并点击Load按钮。 你会看到你的节点回到他们的位置。 让我们反序列化连接并完成我们基于节点的编辑器:private void Load(){var nodesDeserialized = XMLOp.Deserialize<List<Node>>("Assets/Resources/nodes.xml");var connectionsDeserialized = XMLOp.Deserialize<List<Connection>>("Assets/Resources/connections.xml");nodes = new List<Node>();connections = new List<Connection>();foreach (var nodeDeserialized in nodesDeserialized){nodes.Add(new Node(nodeDeserialized.rect.position,nodeDeserialized.rect.width,nodeDeserialized.rect.height,nodeStyle,selectedNodeStyle,inPointStyle,outPointStyle,OnClickInPoint,OnClickOutPoint,OnClickRemoveNode,nodeDeserialized.inPoint.id,nodeDeserialized.outPoint.id));}foreach (var connectionDeserialized in connectionsDeserialized){var inPoint = nodes.First(n => n.inPoint.id == connectionDeserialized.inPoint.id).inPoint;var outPoint = nodes.First(n => n.outPoint.id == connectionDeserialized.outPoint.id).outPoint;connections.Add(new Connection(inPoint, outPoint, OnClickRemoveConnection));}}最后的话这就结束了 。 您现在有一个功能齐全的基于节点的编辑器,具有保存和加载功能。一如既往,下面是完整的脚本。 直到下一次。using UnityEngine;using UnityEditor;using System.Collections.Generic;using System.Linq;public class NodeBasedEditor : EditorWindow{private List<Node> nodes;private List<Connection> connections;private GUIStyle nodeStyle;private GUIStyle selectedNodeStyle;private GUIStyle inPointStyle;private GUIStyle outPointStyle;private ConnectionPoint selectedInPoint;private ConnectionPoint selectedOutPoint;private Vector2 offset;private Vector2 drag;private float menuBarHeight = 20f;private Rect menuBar;[MenuItem("Window/Node Based Editor")]private static void OpenWindow(){NodeBasedEditor window = GetWindow<NodeBasedEditor>();window.titleContent = new GUIContent("Node Based Editor");}private void OnEnable(){nodeStyle = new GUIStyle();nodeStyle.normal.background = EditorGUIUtility.Load("builtin skins/darkskin/images/node1.png") as Texture2D;nodeStyle.border = new RectOffset(12, 12, 12, 12);selectedNodeStyle = new GUIStyle();selectedNodeStyle.normal.background = EditorGUIUtility.Load("builtin skins/darkskin/images/node1 on.png") as Texture2D;selectedNodeStyle.border = new RectOffset(12, 12, 12, 12);inPointStyle = new GUIStyle();inPointStyle.normal.background = EditorGUIUtility.Load("builtin skins/darkskin/images/btn left.png") as Texture2D;inPointStyle.active.background = EditorGUIUtility.Load("builtin skins/darkskin/images/btn left on.png") as Texture2D;inPointStyle.border = new RectOffset(4, 4, 12, 12);outPointStyle = new GUIStyle();outPointStyle.normal.background = EditorGUIUtility.Load("builtin skins/darkskin/images/btn right.png") as Texture2D;outPointStyle.active.background = EditorGUIUtility.Load("builtin skins/darkskin/images/btn right on.png") as Texture2D;outPointStyle.border = new RectOffset(4, 4, 12, 12);}private void OnGUI(){DrawGrid(20, 0.2f, Color.gray);DrawGrid(100, 0.4f, Color.gray);DrawMenuBar();DrawNodes();DrawConnections();DrawConnectionLine(Event.current);ProcessNodeEvents(Event.current);ProcessEvents(Event.current);if (GUI.changed) Repaint();}private void DrawMenuBar(){menuBar = new Rect(0, 0, position.width, menuBarHeight);GUILayout.BeginArea(menuBar, EditorStyles.toolbar);GUILayout.BeginHorizontal();if (GUILayout.Button(new GUIContent("Save"), EditorStyles.toolbarButton, GUILayout.Width(35))){Save();}GUILayout.Space(5);if (GUILayout.Button(new GUIContent("Load"), EditorStyles.toolbarButton, GUILayout.Width(35))){Load();}GUILayout.EndHorizontal();GUILayout.EndArea();}private void DrawGrid(float gridSpacing, float gridOpacity, Color gridColor){int widthDivs = Mathf.CeilToInt(position.width / gridSpacing);int heightDivs = Mathf.CeilToInt(position.height / gridSpacing);Handles.BeginGUI();Handles.color = new Color(gridColor.r, gridColor.g, gridColor.b, gridOpacity);offset += drag * 0.5f;Vector3 newOffset = new Vector3(offset.x % gridSpacing, offset.y % gridSpacing, 0);for (int i = 0; i < widthDivs; i++){Handles.DrawLine(new Vector3(gridSpacing * i, -gridSpacing, 0) + newOffset, new Vector3(gridSpacing * i, position.height, 0f) + newOffset);}for (int j = 0; j < heightDivs; j++){Handles.DrawLine(new Vector3(-gridSpacing, gridSpacing * j, 0) + newOffset, new Vector3(position.width, gridSpacing * j, 0f) + newOffset);}Handles.color = Color.white;Handles.EndGUI();}private void DrawNodes(){if (nodes != null){for (int i = 0; i < nodes.Count; i++){nodes[i].Draw();}}}private void DrawConnections(){if (connections != null){for (int i = 0; i < connections.Count; i++){connections[i].Draw();}}}private void ProcessEvents(Event e){drag = Vector2.zero;switch (e.type){case EventType.MouseDown:if (e.button == 0){ClearConnectionSelection();}if (e.button == 1){ProcessContextMenu(e.mousePosition);}break;case EventType.MouseDrag:if (e.button == 0){OnDrag(e.delta);}break;}}private void ProcessNodeEvents(Event e){if (nodes != null){for (int i = nodes.Count - 1; i >= 0; i--){bool guiChanged = nodes[i].ProcessEvents(e);if (guiChanged){GUI.changed = true;}}}}private void DrawConnectionLine(Event e){if (selectedInPoint != null && selectedOutPoint == null){Handles.DrawBezier(selectedInPoint.rect.center,e.mousePosition,selectedInPoint.rect.center + Vector2.left * 50f,e.mousePosition - Vector2.left * 50f,Color.white,null,2f);GUI.changed = true;}if (selectedOutPoint != null && selectedInPoint == null){Handles.DrawBezier(selectedOutPoint.rect.center,e.mousePosition,selectedOutPoint.rect.center - Vector2.left * 50f,e.mousePosition + Vector2.left * 50f,Color.white,null,2f);GUI.changed = true;}}private void ProcessContextMenu(Vector2 mousePosition){GenericMenu genericMenu = new GenericMenu();genericMenu.AddItem(new GUIContent("Add node"), false, () => OnClickAddNode(mousePosition));genericMenu.ShowAsContext();}private void OnDrag(Vector2 delta){drag = delta;if (nodes != null){for (int i = 0; i < nodes.Count; i++){nodes[i].Drag(delta);}}GUI.changed = true;}private void OnClickAddNode(Vector2 mousePosition){if (nodes == null){nodes = new List<Node>();}nodes.Add(new Node(mousePosition, 200, 50, nodeStyle, selectedNodeStyle, inPointStyle, outPointStyle, OnClickInPoint, OnClickOutPoint, OnClickRemoveNode));}private void OnClickInPoint(ConnectionPoint inPoint){selectedInPoint = inPoint;if (selectedOutPoint != null){if (selectedOutPoint.node != selectedInPoint.node){CreateConnection();ClearConnectionSelection();}else{ClearConnectionSelection();}}}private void OnClickOutPoint(ConnectionPoint outPoint){selectedOutPoint = outPoint;if (selectedInPoint != null){if (selectedOutPoint.node != selectedInPoint.node){CreateConnection();ClearConnectionSelection();}else{ClearConnectionSelection();}}}private void OnClickRemoveNode(Node node){if (connections != null){List<Connection> connectionsToRemove = new List<Connection>();for (int i = 0; i < connections.Count; i++){if (connections[i].inPoint == node.inPoint || connections[i].outPoint == node.outPoint){connectionsToRemove.Add(connections[i]);}}for (int i = 0; i < connectionsToRemove.Count; i++){connections.Remove(connectionsToRemove[i]);}connectionsToRemove = null;}nodes.Remove(node);}private void OnClickRemoveConnection(Connection connection){connections.Remove(connection);}private void CreateConnection(){if (connections == null){connections = new List<Connection>();}connections.Add(new Connection(selectedInPoint, selectedOutPoint, OnClickRemoveConnection));}private void ClearConnectionSelection(){selectedInPoint = null;selectedOutPoint = null;}private void Save(){XMLOp.Serialize(nodes, "Assets/Resources/nodes.xml");XMLOp.Serialize(connections, "Assets/Resources/connections.xml");}private void Load(){var nodesDeserialized = XMLOp.Deserialize<List<Node>>("Assets/Resources/nodes.xml");var connectionsDeserialized = XMLOp.Deserialize<List<Connection>>("Assets/Resources/connections.xml");nodes = new List<Node>();connections = new List<Connection>();foreach (var nodeDeserialized in nodesDeserialized){nodes.Add(new Node(nodeDeserialized.rect.position,nodeDeserialized.rect.width,nodeDeserialized.rect.height,nodeStyle,selectedNodeStyle,inPointStyle,outPointStyle,OnClickInPoint,OnClickOutPoint,OnClickRemoveNode,nodeDeserialized.inPoint.id,nodeDeserialized.outPoint.id));}foreach (var connectionDeserialized in connectionsDeserialized){var inPoint = nodes.First(n => n.inPoint.id == connectionDeserialized.inPoint.id).inPoint;var outPoint = nodes.First(n => n.outPoint.id == connectionDeserialized.outPoint.id).outPoint;connections.Add(new Connection(inPoint, outPoint, OnClickRemoveConnection));}}}using System;using System.Xml.Serialization;using UnityEditor;using UnityEngine;public class Connection{public ConnectionPoint inPoint;public ConnectionPoint outPoint;[XmlIgnore] public Action<Connection> OnClickRemoveConnection;public Connection() { }public Connection(ConnectionPoint inPoint, ConnectionPoint outPoint, Action<Connection> OnClickRemoveConnection){this.inPoint = inPoint;this.outPoint = outPoint;this.OnClickRemoveConnection = OnClickRemoveConnection;}public void Draw(){Handles.DrawBezier(inPoint.rect.center,outPoint.rect.center,inPoint.rect.center + Vector2.left * 50f,outPoint.rect.center - Vector2.left * 50f,Color.white,null,2f);if (Handles.Button((inPoint.rect.center + outPoint.rect.center) * 0.5f, Quaternion.identity, 4, 8, Handles.RectangleCap)){if (OnClickRemoveConnection != null){OnClickRemoveConnection(this);}}}}using System;using System.Xml.Serialization;using UnityEditor;using UnityEngine;public enum ConnectionPointType { In, Out }public class ConnectionPoint{public string id;[XmlIgnore] public Rect rect;[XmlIgnore] public ConnectionPointType type;[XmlIgnore] public Node node;[XmlIgnore] public GUIStyle style;[XmlIgnore] public Action<ConnectionPoint> OnClickConnectionPoint;public ConnectionPoint() { }public ConnectionPoint(Node node, ConnectionPointType type, GUIStyle style, Action<ConnectionPoint> OnClickConnectionPoint, string id = null){this.node = node;this.type = type;this.style = style;this.OnClickConnectionPoint = OnClickConnectionPoint;rect = new Rect(0, 0, 10f, 20f);this.id = id ?? Guid.NewGuid().ToString();}public void Draw(){rect.y = node.rect.y + (node.rect.height * 0.5f) - rect.height * 0.5f;switch (type){case ConnectionPointType.In:rect.x = node.rect.x - rect.width + 8f;break;case ConnectionPointType.Out:rect.x = node.rect.x + node.rect.width - 8f;break;}if (GUI.Button(rect, "", style)){if (OnClickConnectionPoint != null){OnClickConnectionPoint(this);}}}}using System;using System.Xml.Serialization;using UnityEditor;using UnityEngine;public class Node{public Rect rect;[XmlIgnore] public string title;[XmlIgnore] public bool isDragged;[XmlIgnore] public bool isSelected;public ConnectionPoint inPoint;public ConnectionPoint outPoint;[XmlIgnore] public GUIStyle style;[XmlIgnore] public GUIStyle defaultNodeStyle;[XmlIgnore] public GUIStyle selectedNodeStyle;[XmlIgnore] public Action<Node> OnRemoveNode;public Node(){}public Node(Vector2 position, float width, float height, GUIStyle nodeStyle, GUIStyle selectedStyle, GUIStyle inPointStyle, GUIStyle outPointStyle, Action<ConnectionPoint> OnClickInPoint,Action<ConnectionPoint> OnClickOutPoint, Action<Node> OnClickRemoveNode){rect = new Rect(position.x, position.y, width, height);style = nodeStyle;inPoint = new ConnectionPoint(this, ConnectionPointType.In, inPointStyle, OnClickInPoint);outPoint = new ConnectionPoint(this, ConnectionPointType.Out, outPointStyle, OnClickOutPoint);defaultNodeStyle = nodeStyle;selectedNodeStyle = selectedStyle;OnRemoveNode = OnClickRemoveNode;}public Node(Vector2 position, float width, float height, GUIStyle nodeStyle, GUIStyle selectedStyle, GUIStyle inPointStyle, GUIStyle outPointStyle, Action<ConnectionPoint> OnClickInPoint,Action<ConnectionPoint> OnClickOutPoint, Action<Node> OnClickRemoveNode, string inPointID, string outPointID){rect = new Rect(position.x, position.y, width, height);style = nodeStyle;inPoint = new ConnectionPoint(this, ConnectionPointType.In, inPointStyle, OnClickInPoint, inPointID);outPoint = new ConnectionPoint(this, ConnectionPointType.Out, outPointStyle, OnClickOutPoint, outPointID);defaultNodeStyle = nodeStyle;selectedNodeStyle = selectedStyle;OnRemoveNode = OnClickRemoveNode;}public void Drag(Vector2 delta){rect.position += delta;}public void Draw(){inPoint.Draw();outPoint.Draw();GUI.Box(rect, title, style);}public bool ProcessEvents(Event e){switch (e.type){case EventType.MouseDown:if (e.button == 0){if (rect.Contains(e.mousePosition)){isDragged = true;GUI.changed = true;isSelected = true;style = selectedNodeStyle;}else{GUI.changed = true;isSelected = false;style = defaultNodeStyle;}}if (e.button == 1 && isSelected && rect.Contains(e.mousePosition)){ProcessContextMenu();e.Use();}break;case EventType.MouseUp:isDragged = false;break;case EventType.MouseDrag:if (e.button == 0 && isDragged){Drag(e.delta);e.Use();return true;}break;}return false;}private void ProcessContextMenu(){GenericMenu genericMenu = new GenericMenu();genericMenu.AddItem(new GUIContent("Remove node"), false, OnClickRemoveNode);genericMenu.ShowAsContext();}private void OnClickRemoveNode(){if (OnRemoveNode != null){OnRemoveNode(this);}}}using System.IO;using System.Xml.Serialization;public class XMLOp{public static void Serialize(object item, string path){XmlSerializer serializer = new XmlSerializer(item.GetType());StreamWriter writer = new StreamWriter(path);serializer.Serialize(writer.BaseStream, item);writer.Close();}public static T Deserialize<T>(string path){XmlSerializer serializer = new XmlSerializer(typeof(T));StreamReader reader = new StreamReader(path);T deserialized = (T)serializer.Deserialize(reader.BaseStream);reader.Close();return deserialized;}} -
UAVCAN教程(2)uavcan::Node节点的内存管理
2020-12-19 19:29:16libuavcan不直接使用堆分配内存,而是一次性分配一片内存作为内存池来使用,需要我们在创建Node对象时,传入一个表示内存池大小的模板参数,libuavcan会自己基于这片内存池维护内存块的分配和释放。如果我们没有指定...节点uavcan::Node是所有API的基础,它是一个模板类。
内存管理
1、动态内存
libuavcan不直接使用堆分配内存,而是一次性分配一片内存作为内存池来使用,需要我们在创建Node对象时,传入一个表示内存池大小的模板参数,libuavcan会自己基于这片内存池维护内存块的分配和释放。如果我们没有指定模板参数,则要给Node节点的构造函数传入一个我们自定义的分配器的引用,这将在后面讨论。
1.1 内存总量计算
关于这个大小有个计算方式:
主要是考虑该节点要订阅的数据和要发布的数据,比如下面的例子,总共有两个CAN接口,节点要发布数据类型A和B,A、B类型数据序列化后的最大长度是100和24,发布A的发布者有两个,发布B的发布者有32个,直接进行算数运算:
2 * ((3 * 100 + 32 * 24)对于接收的数据也一样,只不过接收的接口数要比实际加1,估计是留有余量。
需要的内存总数是:2 * ((3 * 100 + 32 * 24) + (2 + 1) * (256 + 10)) = 3732 bytes Round up to 64: 3776 bytes.
该结果要64字节对齐。
1.2 内存获取
libuavcan提供了查询当前内存使用量以及峰值使用量的方法,通过PoolAllocator<>类来获取。
- uint16_t getNumUsedBlocks() const - 当前内存块使用量 - uint16_t getNumFreeBlocks() const - 当前内存块空闲数量 - uint16_t getPeakNumUsedBlocks() const - 峰值使用量
那么这个PoolAllocator类哪来呢,很显然,必定是节点Node的一个成员,所以可以这样获取:
std::cout << node.getAllocator().getNumUsedBlocks() << std::endl; std::cout << node.getAllocator().getNumFreeBlocks() << std::endl; std::cout << node.getAllocator().getPeakNumUsedBlocks() << std::endl;
备注:上面的node是一个Node对象。
1.3 定制内存分配器
PoolAllcator就是我们前面所说的内存分配器,在创建Node节点的时候,如果传入了内存总数,则Node内部会自己创建内存分配器;如果我们不传入内存总数,则需要自己创建PoolAllcator,然后传给Node。
前面说节点采用一次性静态内存分配的方式来,这只不过是默认选择,其实libuavcan也实现了其他可选的内存分配器,如基于堆的动态内存分配,见uavcan/helpers/heap_based_pool_allocator.hpp,我们通常没有这个需求,所以不讨论。
2、栈内存
消息和服务对象在栈上分配,也就是说通过回调函数传递给应用的消息对象在出了回调函数之后就消失了。
-
PyTorch图神经网络实践(四)Node2Vec节点分类及其可视化
2020-11-02 17:30:48关于Node2Vec的介绍有很多了,...创建Node2Vec模型 训练和测试数据 TSNE降维后可视化 完整代码如下: import torch import matplotlib.pyplot as plt from sklearn.manifold import TSNE from torch_geometric.data关于Node2Vec的介绍有很多了,这里就不细述。本文主要是介绍如何用PyTorch Geometric快速实现Node2Vec节点分类,并对其结果进行可视化。
整个过程包含四个步骤:
- 导入图数据(这里以Cora为例)
- 创建Node2Vec模型
- 训练和测试数据
- TSNE降维后可视化
完整代码如下:
import torch import matplotlib.pyplot as plt from sklearn.manifold import TSNE from torch_geometric.datasets import Planetoid from torch_geometric.nn import Node2Vec dataset = Planetoid(root='/tmp/Cora', name='Cora') data = dataset[0] device = 'cuda' if torch.cuda.is_available() else 'cpu' model = Node2Vec(data.edge_index, embedding_dim=128, walk_length=20, context_size=10, walks_per_node=10, num_negative_samples=1, sparse=True).to(device) loader = model.loader(batch_size=128, shuffle=True, num_workers=4) optimizer = torch.optim.SparseAdam(model.parameters(), lr=0.01) def train(): model.train() total_loss = 0 for pos_rw, neg_rw in loader: optimizer.zero_grad() loss = model.loss(pos_rw.to(device), neg_rw.to(device)) loss.backward() optimizer.step() total_loss += loss.item() return total_loss / len(loader) @torch.no_grad() def test(): model.eval() z = model() acc = model.test(z[data.train_mask], data.y[data.train_mask], z[data.test_mask], data.y[data.test_mask], max_iter=150) return acc for epoch in range(1, 101): loss = train() acc = test() print(f'Epoch: {epoch:02d}, Loss: {loss:.4f}, Acc: {acc:.4f}') @torch.no_grad() def plot_points(colors): model.eval() z = model(torch.arange(data.num_nodes, device=device)) z = TSNE(n_components=2).fit_transform(z.cpu().numpy()) y = data.y.cpu().numpy() plt.figure(figsize=(8, 8)) for i in range(dataset.num_classes): plt.scatter(z[y == i, 0], z[y == i, 1], s=20, color=colors[i]) plt.axis('off') plt.show() colors = ['#ffc0cb', '#bada55', '#008080', '#420420', '#7fe5f0', '#065535', '#ffd700'] plot_points(colors)
输出结果如下:
Epoch: 01, Loss: 8.0489, Acc: 0.1780 Epoch: 02, Loss: 6.0371, Acc: 0.2000 Epoch: 03, Loss: 4.9573, Acc: 0.2190 Epoch: 04, Loss: 4.1341, Acc: 0.2540 Epoch: 05, Loss: 3.4723, Acc: 0.2920 Epoch: 06, Loss: 2.9627, Acc: 0.3250 Epoch: 07, Loss: 2.5404, Acc: 0.3550 Epoch: 08, Loss: 2.2190, Acc: 0.3840 Epoch: 09, Loss: 1.9515, Acc: 0.4200 Epoch: 10, Loss: 1.7386, Acc: 0.4470 Epoch: 11, Loss: 1.5671, Acc: 0.4670 Epoch: 12, Loss: 1.4274, Acc: 0.4890 Epoch: 13, Loss: 1.3167, Acc: 0.5060 Epoch: 14, Loss: 1.2284, Acc: 0.5340 Epoch: 15, Loss: 1.1606, Acc: 0.5530 Epoch: 16, Loss: 1.1018, Acc: 0.5580 Epoch: 17, Loss: 1.0573, Acc: 0.5850 Epoch: 18, Loss: 1.0241, Acc: 0.5970 Epoch: 19, Loss: 0.9943, Acc: 0.6070 Epoch: 20, Loss: 0.9701, Acc: 0.6080 Epoch: 21, Loss: 0.9523, Acc: 0.6230 Epoch: 22, Loss: 0.9342, Acc: 0.6290 Epoch: 23, Loss: 0.9200, Acc: 0.6360 Epoch: 24, Loss: 0.9104, Acc: 0.6460 Epoch: 25, Loss: 0.9003, Acc: 0.6350 Epoch: 26, Loss: 0.8936, Acc: 0.6440 Epoch: 27, Loss: 0.8859, Acc: 0.6590 Epoch: 28, Loss: 0.8776, Acc: 0.6570 Epoch: 29, Loss: 0.8730, Acc: 0.6590 Epoch: 30, Loss: 0.8696, Acc: 0.6660 Epoch: 31, Loss: 0.8658, Acc: 0.6740 Epoch: 32, Loss: 0.8609, Acc: 0.6740 Epoch: 33, Loss: 0.8586, Acc: 0.6750 Epoch: 34, Loss: 0.8559, Acc: 0.6770 Epoch: 35, Loss: 0.8524, Acc: 0.6800 Epoch: 36, Loss: 0.8516, Acc: 0.6880 Epoch: 37, Loss: 0.8494, Acc: 0.6820 Epoch: 38, Loss: 0.8481, Acc: 0.6770 Epoch: 39, Loss: 0.8464, Acc: 0.6800 Epoch: 40, Loss: 0.8435, Acc: 0.6850 Epoch: 41, Loss: 0.8423, Acc: 0.6890 Epoch: 42, Loss: 0.8401, Acc: 0.6870 Epoch: 43, Loss: 0.8392, Acc: 0.6900 Epoch: 44, Loss: 0.8390, Acc: 0.6850 Epoch: 45, Loss: 0.8365, Acc: 0.6880 Epoch: 46, Loss: 0.8363, Acc: 0.6920 Epoch: 47, Loss: 0.8354, Acc: 0.6990 Epoch: 48, Loss: 0.8345, Acc: 0.6970 Epoch: 49, Loss: 0.8352, Acc: 0.6970 Epoch: 50, Loss: 0.8350, Acc: 0.7040 Epoch: 51, Loss: 0.8333, Acc: 0.6970 Epoch: 52, Loss: 0.8320, Acc: 0.6980 Epoch: 53, Loss: 0.8321, Acc: 0.6960 Epoch: 54, Loss: 0.8325, Acc: 0.6940 Epoch: 55, Loss: 0.8312, Acc: 0.7010 Epoch: 56, Loss: 0.8298, Acc: 0.7040 Epoch: 57, Loss: 0.8294, Acc: 0.6990 Epoch: 58, Loss: 0.8296, Acc: 0.6960 Epoch: 59, Loss: 0.8302, Acc: 0.7050 Epoch: 60, Loss: 0.8286, Acc: 0.7030 Epoch: 61, Loss: 0.8298, Acc: 0.7020 Epoch: 62, Loss: 0.8292, Acc: 0.7010 Epoch: 63, Loss: 0.8288, Acc: 0.7090 Epoch: 64, Loss: 0.8284, Acc: 0.6990 Epoch: 65, Loss: 0.8267, Acc: 0.6970 Epoch: 66, Loss: 0.8274, Acc: 0.6950 Epoch: 67, Loss: 0.8279, Acc: 0.6940 Epoch: 68, Loss: 0.8274, Acc: 0.6940 Epoch: 69, Loss: 0.8278, Acc: 0.7000 Epoch: 70, Loss: 0.8258, Acc: 0.7000 Epoch: 71, Loss: 0.8283, Acc: 0.6990 Epoch: 72, Loss: 0.8257, Acc: 0.6990 Epoch: 73, Loss: 0.8262, Acc: 0.7060 Epoch: 74, Loss: 0.8260, Acc: 0.7120 Epoch: 75, Loss: 0.8259, Acc: 0.7140 Epoch: 76, Loss: 0.8266, Acc: 0.7060 Epoch: 77, Loss: 0.8254, Acc: 0.7070 Epoch: 78, Loss: 0.8261, Acc: 0.7030 Epoch: 79, Loss: 0.8258, Acc: 0.6980 Epoch: 80, Loss: 0.8253, Acc: 0.6950 Epoch: 81, Loss: 0.8256, Acc: 0.7050 Epoch: 82, Loss: 0.8252, Acc: 0.7070 Epoch: 83, Loss: 0.8238, Acc: 0.7060 Epoch: 84, Loss: 0.8253, Acc: 0.7060 Epoch: 85, Loss: 0.8251, Acc: 0.7070 Epoch: 86, Loss: 0.8255, Acc: 0.7090 Epoch: 87, Loss: 0.8251, Acc: 0.7160 Epoch: 88, Loss: 0.8247, Acc: 0.7140 Epoch: 89, Loss: 0.8246, Acc: 0.7020 Epoch: 90, Loss: 0.8245, Acc: 0.7050 Epoch: 91, Loss: 0.8250, Acc: 0.7160 Epoch: 92, Loss: 0.8249, Acc: 0.7100 Epoch: 93, Loss: 0.8249, Acc: 0.7040 Epoch: 94, Loss: 0.8245, Acc: 0.7060 Epoch: 95, Loss: 0.8252, Acc: 0.7030 Epoch: 96, Loss: 0.8244, Acc: 0.6990 Epoch: 97, Loss: 0.8242, Acc: 0.7030 Epoch: 98, Loss: 0.8244, Acc: 0.7050 Epoch: 99, Loss: 0.8236, Acc: 0.6990 Epoch: 100, Loss: 0.8233, Acc: 0.7030
可视化效果如下:
Cora数据集中一共有七种节点,所以使用了七种颜色。从分类结果看,准确率只有0.7左右,并不是很高,所以可视化效果也不是特别好,有些类别混杂在一起了。可以考虑使用其他方法进行改进。
-
通过节点类创建和打印链表
2019-03-19 21:08:44构造函数是使用了默认数据域为0,默认指针域为NULL #include<iostream>...//用结构体类型表示一个节点 class Node { public: typedef double value_type; Node(const value_type&...构造函数是使用了默认数据域为0,默认指针域为NULL
#include<iostream> #include<cstdlib>//size_t using namespace std; //用结构体类型表示一个节点 class Node { public: typedef double value_type; Node(const value_type& init_data = 0, Node* init_link = NULL) {//默认构造函数的数据域为0 data_field = init_data; link_field = init_link; } void set_data(const value_type& value) { data_field = value; } void set_link(Node* link) { link_field = link; } value_type data() { return data_field; } Node* link() { return link_field; } private: value_type data_field; Node* link_field; }; Node* create_list(int num) { Node::value_type head_data; cin >> head_data; Node* head_ptr = new Node(head_data);//我的头节点是有东西的 Node* pre_ptr = head_ptr;//定义一个前向节点为头节点,后面就通过这个指针连起来 for (int i = 1; i < num; ++i) { Node::value_type temp; cin >> temp; Node* new_ptr = new Node(temp); pre_ptr->set_link(new_ptr);//将前一个指针指向新节点 pre_ptr = new_ptr;//然后更新该节点 } return head_ptr; } //输出链表 void display_list(Node* head_ptr) { Node* cursor = head_ptr; for (; cursor != NULL; cursor = cursor->link()) { cout << cursor->data()<<' '; } cout << endl; } int main() { int n = 5; Node* head_ptr = create_list(n);//输入1 2 3 4 5 display_list(head_ptr);//1 2 3 4 5 while(1); }
解析:
首先创建头节点,输入头节点数据域,指针域由构造函数可知指向NULL,然后循环创建节点,通过pre_ptr连接所以节点即可
步骤一、pre_ptr指向头节点,创建新节点
步骤二、通过pre_ptr连接节点:设置pre_ptr的指针域为新节点,然后更新pre_ptr
步骤三、重复步骤二,知道创建的节点到达预设的num即可.
(完)
-
snapdragon-node:Snapdragon实用程序,用于以自定义代码(例如插件)创建新的AST节点-源码
2021-01-29 17:41:07用于创建AST节点的类。 请考虑关注该项目的作者 ,并考虑为该项目以显示您的 :red_heart_selector: 和支持。 安装 使用安装: $ npm install --save snapdragon-node 用法 const Node = require ( 'snapdragon-... -
xmldocument如何创建一个不带结尾的节点_Nuke Python创建Read节点小技巧你知道吗?
2020-12-09 02:02:09用过Nuke的童鞋都知道我们可以拖拽mov或者文件夹素材到Nuke的Node Graph中,它可以自动分类这些素材形成几个Read节点,并自动识别素材的帧数范围以及Metadata关于素材的所有信息。那么在编写工具的时候如果遇到需要... -
Node-RED系列(八):Node-RED网络节点的使用
2021-01-31 19:38:00上一期我们讲到了Node-RED中存储file分类节点的使用,利用存储节点我们可以创建文件,追加内容到指定文件, 监听一些文件的写入 本篇文章我来给大家讲一下Node-RED中网络network分类下的节点,该分类下有12个节点, ... -
在Drupal7里如何写代码创建节点、评论和分类
2011-06-05 09:30:001.怎样写代码创建一个节点 1.1初始化一个节点对象$node = new stdClass(); //创建一个节点对象$node->type = "page"; // 指定节点的类型$node->title = "你的节点的标题";$node->language = LANGUAGE_NONE; // 如果... -
单链表的节点类以及接口类的定义
2020-04-26 23:31:18所以首先我们需要一个结点类,以定义数据类型和指针域,结点类代码如下: public class Node { //数据 int data; //下一个节点 Node next; Node before; //创建一个无参数构造方法,用于初始化 public Node... -
二叉排序树,完成创建节点,插入节点,删除节点,查找节点,中序遍历的功能
2011-09-24 16:16:24#include "stdafx.h" #include #include using namespace std; //二叉排序树,完成创建节点,插入节点,删除节点,查找节点,中序遍历的功能 //节点类定义 class Node{ public: int -
/单向链表操作/ 节点对换 C++版
2016-09-20 21:19:03/单向链表操作/ 节点对换 ...创建node节点类ListNode,与链表类LinkList,属性均为public#include using namespace std; #define ok 0 #define error -1 class ListNode { public: int data; ListNode *next; List -
利用类对象为节点创建简单单向链表
2015-05-03 10:06:25//Node.h #include using namespace std; class Node { public: Node(int data) { this->data = data; this->next = NULL; } ~Node() {} static Node * pHead;//因为头尾指针不属于某一个 -
Java实现创建简单的链表操作
2019-03-27 10:49:101、创建Node节点类 //构建节点类 public class Node { int data; int np; String names; Node next; //节点声明的构造函数 public Node(int data, int np, String names) { this.data = data; this.np =... -
ElasticSearch 源码分析三 节点Node初始化 && 启动
2019-11-23 13:09:101.创建nodeEnvironment,节点的环境信息 2.读取jvm信息 jvmInfo 3.创建pluginService对象,会加载所有的模块和插件 4.然后调用pluginService更新settings配置文件 5.再初始化最终的environment 6.创建一个... -
Unity 可视化编辑工具 树节点 Tree Node Editor 四
2018-02-02 11:08:00Unity 可视化编辑工具 树节点 Tree Node Editor 最近想写一个行为树编辑器,终于找到编辑显示节点和连线的...创建一个节点类 NodeRoot 不需要放在 Editor 文件夹下 using System.Collections.Generic; using Uni... -
关于Python二叉树及节点的创建与遍历
2020-07-28 17:08:23创建节点类 class Node(object): def init(self, item): self.item = item self.lchild = None # 左孩子 self.rchild = None # 右孩子 创建树类 class BinaryTree(object): def init(self, node=None): self.... -
利用类对象为节点创建简单单向链表 ...
2015-05-03 10:06:00//Node.h #include <iostream> using namespace std; class Node { public: Node(int data) { this->data = data; this->next = NULL; } ~Node() {} static N... -
栈的创建-----用链表实现栈
2018-11-01 09:09:031、创建Node节点类(存储链表的前节点和本节点存储的元素) 2、节点存储的是泛型数据 3、创建一个栈的接口----定义如下函数: 4、接口实现类(栈顶元素指针和链表元素计数器) 代码实现: 接口类:StackADT ... -
二叉树的节点表示以及树的创建
2020-02-12 17:31:27文章目录二叉树的节点表示树的创建 ... """节点类""" # elem为本身的值,lchild为左孩子,rchild为有孩子 def __init__(self, elem=-1, lchild=None, rchild=None): self.elem = elem self.lchil... -
cocos creator基础-cc.Node(四)节点action的使用
2019-05-07 14:58:591: Action类是动作命令,我们创建Action,然后节点运行action就能够执行Action的动作; 2: Action分为两类: (1) 瞬时就完成的ActionInstant, (2) 要一段时间后才能完成ActionIntervial; 3: cc.Node runAction: 节点... -
custom-node-stack:bluecompute的自定义节点堆栈-源码
2021-02-18 20:44:19Node.js堆栈 Node.js堆栈提供了开发应用程序的一致方法。 作为异步事件驱动JavaScript运行时,Node旨在构建可扩展的网络应用程序。 该堆栈基于Node.js v12运行时,并允许您使用Appsody开发新的或现有的Node.js应用... -
Something wrong when append node. ## 摘要:【CITA】新增普通节点出错
2020-11-26 10:22:27<div><p>摘要:【CITA】新增普通节点出错 ...前提条 操作步骤:...可以正常创建普通节点。 实际结果:创建普通节点报错。</p><p>该提问来源于开源项目:citahub/cita</p></div> -
ROS 2 C++成员函数方式创建节点 函数调用
2020-05-04 15:37:43ROS2 的主函数的一般形式如下面的代码所示,实际使用时,当我在类里面创建了一个成员函数的时候,发现无法调用到该函数。 int main(int argc, char **argv) { // RCLCPP_INFO(LOGGER, "Initialize node"); rclcpp:... -
Python中二叉树的节点表示以及树的创建
2019-04-01 20:14:45#通过使用Node类中定义三个属性,分别为elem本身的值,还有lchild左孩子和rchild右孩子 class Node(object): ...#树的创建,创建一个树的类,并给一个root根节点,一开始为空,随后添加节点 cl... -
Java多叉树的创建,遍历,节点插入,删除,修改及节点路径获取
2017-12-21 21:35:56学生党一枚。最近课程设计做了个多叉树的书目分类管理系统。但愿有点用吧。... 原创 可能会有很多不足 请多指教 public class Node { private String id; //节点id private String name; //节点名称 private -
Ogre中实现动画效果之节点动画(Node Animation)
2017-03-15 17:32:06Ogre中与节点动画相关的类: (1)Ogre::Animation (2)Ogre::AnimationState (3)Ogre::NodeAnimationTrack (4)Ogre::TransformKeyFrame 节点动画的使用步骤: //1.创建动画对象... -
数据结构(九)---赫夫曼编码
2020-03-18 00:08:06//创建Node节点类,带权值 //它可以使继承他的类进行比较大小,只需要调用实现类的compareTo方法即可 class Node implements Comparable<Node>{ public Byte data;//存放数据本身,字符a转化成ASCII码97,... -
modern-node-template:用于创建针对现代Node.JS的包的模板存储库(> = 14)-源码
2021-02-24 09:04:29模板存储库,用于创建针对现代Node.JS(> = 14)的程序包,并支持本机ECMAScript模块,JSDoc TypeScript输入,私有类字段和其他ES2020功能。 ESLint 7配置为支持ES2020功能,包括随附的提案(通过@ typescript-... -
子节点
2019-07-18 07:59:02创建元素节点. 首先我说一下插入新的节点方法:appendChild(),插入节点appendChild()是在指定节点的最后一个子节点列表之后添加一个新的子节点。它语法是:appendChild(newnode),所以它的参数也就是newnode,而new...
-
中国移动设备用户体验调研报告
-
Cmake 教程
-
LVS + Keepalived 实现 MySQL 负载均衡与高可用
-
nflgame:用于检索和读取NFL Game Center JSON数据的API。 它可以处理实时数据,可用于幻想足球-源码
-
数据库设计StepbyStep
-
从设计到策划——我的成长经历
-
shockwave网站::rocket:ShockWave Inc.网站-源码
-
TypeError: Class advice impossible in Python3. Use the @Implementer class decorator instead.
-
响应式编程入门与实战(Reactor、WebFlux、R2DBC)
-
迪普食品-源码
-
让界面更加清爽
-
nasm: error: more than one input file specified 原因
-
MMM 集群部署实现 MySQL 高可用和读写分离
-
PHP深入理解-PHP架构布局
-
Jsplumb从入门到实战
-
网页元素轻设计–尊重用户产品体验
-
平面型四光纤耦合系统的研究
-
环境气体中激光诱导Fe等离子体发射光谱的时间演化特性
-
华为1+X——网络系统建设与运维(高级)
-
Prisma初体验【逆向生成数据模型】