几个比较重要的类和接口:

Canvas、CanvasUpdateRegistry、ClipperRegistry、LayoutRebuilder、LayoutGroup、Graphics、MaskableGraphic。

ICanvasElement、ILayoutElement。

刷新的大致过程:由Canvas控制,通过 ICanvasElement 接口,使用脏标记方法SetDirty()来统一更新CanvasElement。

几个问题:脏标记法是什么?SetDirty()具体在哪些地方被调用?Rebuild的具体过程是什么?如何进行优化?这些问题将在下面解决。

目录

1 基础

1.1 脏标记方法

1.2 Canvas

1.3 子Canvas

1.4 Graphic

1.5 Layout

1.6 网格重建分为2部分:一个是Batch,一个是ReBuild

2 Canvas

2.1 Canvas.WillRenderCanvases事件

2.2 CanvasUpdateRegistry

2.3 PerformUpdate

3 Rebuild

3.1 Layout的Rebuild

3.2 Graphic的Rebuild

3.3 Rebatch和Rebuild的触发条件总结

4 优化

4.1 动静分离

4.2 其它


1 基础

1.1 脏标记方法

脏标识模式:脏标识模式 · Optimization Patterns · 游戏设计模式

将工作推迟到必要时进行,以免做没必要的工做,比如被销毁的物体的计算、父子关系的物体重复计算。

1.2 Canvas

Canvas是一个Native层实现的Unity组件,被Unity渲染系统用于在游戏世界空间中渲染分层几何体(layered geometry)。 Canvas负责把它们包含的Mesh合批,生成合适的渲染命令发送给Unity图形系统。以上行为都是在Native C++代码中完成,我们称之为Rebatch或者Batch Build,当一个Canvas中包含的几何体需要Rebacth时,这个Canvas就会被标记为Dirty状态。

几何图形由Canvas Renderer 组件提供给 Canvases 。

1.3 子Canvas

Canvas组件可以嵌套在另一个Canvas组件下,我们称为子Canvas,子Canvas可以把它的子物体与父Canvas分离,使得当子Canvas被标记为Dirty时,并不会强制让父Canvas也强制Rebuild,反之亦然。但在某些特殊情况下,使用子Canvas进行分离的方法可能会失效,例如当对父Canvas的更改导致子Canvas的大小发生变化时。

1.4 Graphic

Graphic是Image、RawImage、Text类的基类。大多数Unity内置的继承Graphic的类都是通过继承一个叫MaskableGraphic的子类来实现,这使得他们可以通过IMaskable接口来被隐藏。

1.5 Layout

Layout控制着RectTransform的大小和位置,通常用于创建复杂的布局,这些布局需要对其内容进行相对大小调整或相对位置调整。Layout仅依赖于RectTransforms,并且仅影响其关联RectTransforms的属性。这些Layout类不依赖于Graphic类,可以独立于UGUI的Graphic类之外使用。

1.6 网格重建分为2部分:一个是Batch,一个是ReBuild

Batch:就是Canvas 负责将其子节点的 UI 元素网格合并,并生成相应的渲染命令再发送到 Unity 的图形管道的过程。

通俗来讲,Canvas 就是渲染 UI 的组件,所以当 UI 变化了,它就要执行一次 Batch,给 GPU 进行渲染。

Rebuild:Layout和Graphic的更新称为Rebuild,指重新计算布局和网格的过程,这个过程在CanvasUpdateRegistry中执行。 在CanvasUpdateRegistry中,最重要的方法是PerformUpdate。每当Canvas组件调用WillRenderCanvases事件时,就会调用此方法。此事件每帧调用一次。

rebuild是batch的子操作,一次batch需要各组件执行自己的rebuild操作。

2 Canvas

2.1 Canvas.WillRenderCanvases事件

当Canvas需要重绘时会调用Canvas.SendWillRenderCanvases()方法。

  public sealed class Canvas : Behaviour{public delegate void WillRenderCanvases();public static event Canvas.WillRenderCanvases willRenderCanvases;
​public static void ForceUpdateCanvases() => Canvas.SendWillRenderCanvases();
​[RequiredByNativeCode]private static void SendWillRenderCanvases(){if (Canvas.willRenderCanvases == null)return;Canvas.willRenderCanvases();}}

SendWillRenderCanvas()方法中调用Canvas.willRenderCanvases()事件。

2.2 CanvasUpdateRegistry

CanvasUpdateRegistry(画布更新注册处)是一个单例,它是UGUI与Canvas之间的中介,继承了ICanvasElement接口的组件都可以注册到它,它监听了Canvas即将渲染的事件,并调用已注册组件的Rebuild等方法。

CanvasUpdateRegistry的构造函数:

    //CanvasUpdateRegistry 被初始化时向Canvas中注册了更新函数(PerformUpdate),以用来响应重建。protected CanvasUpdateRegistry(){Canvas.willRenderCanvases += PerformUpdate;}

willRenderCanvases是Canvas的静态事件,事件是一种特殊的委托,在渲染所有的Canvas之前,抛出willRenderCanvases事件,继而调用CanvasUpdateRegistry的PerformUpdate方法。

CanvasUpdateRegistry维护了两个索引集(不会存放相同的元素):

    //IndexedSet是Unity中吸取了List和Dictionary各自优点的一种容器private readonly IndexedSet<ICanvasElement> m_LayoutRebuildQueue = new IndexedSet<ICanvasElement>();private readonly IndexedSet<ICanvasElement> m_GraphicRebuildQueue = new IndexedSet<ICanvasElement>();

m_LayoutRebuildQueue:保存着需要重建的布局元素(一般是通过LayoutGroup布局改变的UI)

m_GraphicRebuildQueue:需要重建的Graphics元素(如Image,RawIamge,Text的贴图,材质,宽高发生变化)

m_LayoutRebuildQueue是通过RegisterCanvasElementForLayoutRebuild和TryRegisterCanvasElementForLayoutRebuild方法添加元素。

m_GraphicRebuildQueue是通过RegisterCanvasElementForGraphicRebuild和TryRegisterCanvasElementForGraphicRebuild方法添加元素。

二者通过UnRegisterCanvasElementForRebuild移除注册元素。

2.3 PerformUpdate

ICanvasElement接口:

public interface ICanvasElement
{void Rebuild(CanvasUpdate executing);//重建方法Transform transform{get;}void LayoutComplete();//布局重建完成void GraphicUpdateComplete();//图像重建完成bool IsDestroyed();//检查Element是否无效
}

CanvasUpdate枚举:

public enum CanvasUpdate
{Prelayout = 0,//预布局Layout = 1,//布局PostLayout = 2,//后期布局PreRender = 3,//预渲染LatePreRender = 4,//后期预渲染MaxUpdateValue = 5
}

除了最后一个枚举项,其他五个项分别代表了布局的三个阶段和渲染的两个阶段。

在PerformUpdate方法中

  1. 从两个序列中删除不可用的元素 CleanInvalidItems();

  2. 重建布局(Layout Rebuild)开始

  3. 对m_LayoutRebuildQueue(被标记为Dirty状态的Layout对象)依据父对象的数量进行排序,父transform少的在前

  4. 分别以PreLayout,Layout,PostLayout的参数顺序调用每一个元素的Rebuild方法

  5. 调用所有元素的LayoutComplete方法

  6. 清除布局重建序列中的所有元素

  7. 重建布局结束

  8. 完成布局后,调用组件的裁剪方法ClippingRegistry.Cull()

  9. 重建图形(Graphic Rebuild)开始

  10. 对m_GraphicRebuildQueue(被标记了Dirty状态的Graphic对象)以PreRender,LatePreRender的参数顺序调用每一个元素(无序)的Rebulid方法

  11. 调用所有元素的GraphicUpdateComplete方法

  12. 清除图形重建序列中的所有元素

  13. 重建图形结束

CanvasUpdateRegistry.cs中的PerformUpdate():

   
 private void PerformUpdate(){UISystemProfilerApi.BeginSample(UISystemProfilerApi.SampleType.Layout);//从两个序列中删除不可用的元素CleanInvalidItems();//重建布局(Layout Rebuild)开始m_PerformingLayoutUpdate = true;//这个bool值用来锁住Rebuild期间的remove、SetDirty等操作,下同//依据父对象的数量进行排序,父transform少的在前,即从上到下进行Rebuildm_LayoutRebuildQueue.Sort(s_SortLayoutFunction);//分别以PreLayout,Layout,PostLayout的参数顺序调用每一个元素的Rebuild方法for (int i = 0; i <= (int)CanvasUpdate.PostLayout; i++){for (int j = 0; j < m_LayoutRebuildQueue.Count; j++){var rebuild = instance.m_LayoutRebuildQueue[j];try{if (ObjectValidForUpdate(rebuild))//元素存在且为Objectrebuild.Rebuild((CanvasUpdate)i);//调用元素各自的Rebuild方法}catch (Exception e){Debug.LogException(e, rebuild.transform);}}}
​//调用所有元素的LayoutComplete方法for (int i = 0; i < m_LayoutRebuildQueue.Count; ++i)m_LayoutRebuildQueue[i].LayoutComplete();
​instance.m_LayoutRebuildQueue.Clear();//清空队列m_PerformingLayoutUpdate = false;//解锁
​// now layout is complete do culling...//重建布局结束,完成布局后,调用组件的裁剪方法ClipperRegistry.instance.Cull();
​//重建图形(Graphic Rebuild)开始m_PerformingGraphicUpdate = true;//上锁//以PreRender,LatePreRender的参数顺序调用每一个元素的Rebulid方法,元素顺序无序for (var i = (int)CanvasUpdate.PreRender; i < (int)CanvasUpdate.MaxUpdateValue; i++){for (var k = 0; k < instance.m_GraphicRebuildQueue.Count; k++){try{var element = instance.m_GraphicRebuildQueue[k];if (ObjectValidForUpdate(element))//元素存在且为Objectelement.Rebuild((CanvasUpdate)i);//调用元素各自的Rebuild方法}catch (Exception e){Debug.LogException(e, instance.m_GraphicRebuildQueue[k].transform);}}}
​//调用所有元素的LayoutComplete方法for (int i = 0; i < m_GraphicRebuildQueue.Count; ++i)m_GraphicRebuildQueue[i].GraphicUpdateComplete();
​instance.m_GraphicRebuildQueue.Clear();//清空队列m_PerformingGraphicUpdate = false;//解锁UISystemProfilerApi.EndSample(UISystemProfilerApi.SampleType.Layout);}

图源:两水先木示【Unity】UGUI优化_两水先木示的博客-CSDN博客

3 Rebuild

Rebuild 分为 Layout Rebuild 和 Graphic Rebuild

3.1 Layout的Rebuild

Layout元素:HorizontalLayoutGroup、VerticalLayoutGroup、GridLayoutGroup、ScrollRect等。

重新计算一个 Layout 组件子节点的位置或大小。

LayoutGroup.cs中的SetDirty() 函数:

    protected void SetDirty(){if (!IsActive())return;
​if (!CanvasUpdateRegistry.IsRebuildingLayout())LayoutRebuilder.MarkLayoutForRebuild(rectTransform);elseStartCoroutine(DelayedSetDirty(rectTransform));}
​IEnumerator DelayedSetDirty(RectTransform rectTransform){yield return null;LayoutRebuilder.MarkLayoutForRebuild(rectTransform);}

接下来:LayoutRebuilder.MarkLayoutForRebuild→LayoutRebuilder.MarkLayoutRootForRebuild→CanvasUpdateRegistry.TryRegisterCanvasElementForLayoutRebuild→CanvasUpdateRegistry.InternalTryRegisterCanvasElementForLayoutRebuild→m_LayoutRebuildQueue.AddUnique

LayoutGroup中SetDirty()调用的具体情况:

  • SetProperty

  • OnEnable

  • OnDidApplyAnimationProperties (动画修改属性时)

  • OnRectTransformDimensionsChange(RectTransform的Anchor,Width,Height,Anchor,Pivot改变时调用,注意改变Position,Rotation,Scale不会调用。)

  • OnTransformChildrenChanged(子物体改变时)

  • 当 LayoutGroup 的直接子节点,并且是 Graphic 类型的(Image、RawImage、Text),被 SetLayoutDirty 的时候,该 LayoutGroup 也会被加入到 Rebuild 的队列中。

  • 编辑器模式下OnValidate时。

Layout重建时过程:

先自下而上地执行Layout元素的CalculateLayoutInputHorizontal/ CalculateLayoutInputHorizontal方法进行计算布局大小、行数、列数等内容。

布局计算需要自下而上执行,子在父之前完成,因为父计算的大小依赖于子的大小。

然后自上而下地执行Layout元素的SetLayoutHorizontal/ SetLayoutVertical方法进行调整子物体的位置或调整自身大小等事情。

布局控制需要自上而下执行,父在子之前完成, 因为子依赖于父的大小。

LayoutRebuilder.cs中的Rebuild():

    public void Rebuild(CanvasUpdate executing){switch (executing){case CanvasUpdate.Layout:PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputHorizontal());PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutHorizontal());PerformLayoutCalculation(m_ToRebuild, e => (e as ILayoutElement).CalculateLayoutInputVertical());PerformLayoutControl(m_ToRebuild, e => (e as ILayoutController).SetLayoutVertical());break;}}

LayoutRebuilder.cs中的PerformLayoutCalculation()和PerformLayoutControl():

private void PerformLayoutCalculation(RectTransform rect, UnityAction<Component> action){if (rect == null)return;var components = ListPool<Component>.Get();rect.GetComponents(typeof(ILayoutElement), components);StripDisabledBehavioursFromList(components);// If there are no controllers on this rect we can skip this entire sub-tree// We don't need to consider controllers on children deeper in the sub-tree either,// since they will be their own roots.if (components.Count > 0  || rect.GetComponent(typeof(ILayoutGroup))){// Layout calculations needs to executed bottom up with children being done before their parents,// because the parent calculated sizes rely on the sizes of the children.for (int i = 0; i < rect.childCount; i++)PerformLayoutCalculation(rect.GetChild(i) as RectTransform, action);for (int i = 0; i < components.Count; i++)action(components[i]);}ListPool<Component>.Release(components);} private void PerformLayoutControl(RectTransform rect, UnityAction<Component> action){if (rect == null)return;var components = ListPool<Component>.Get();rect.GetComponents(typeof(ILayoutController), components);StripDisabledBehavioursFromList(components);// If there are no controllers on this rect we can skip this entire sub-tree// We don't need to consider controllers on children deeper in the sub-tree either,// since they will be their own roots.if (components.Count > 0){// Layout control needs to executed top down with parents being done before their children,// because the children rely on the sizes of the parents.// First call layout controllers that may change their own RectTransformfor (int i = 0; i < components.Count; i++)if (components[i] is ILayoutSelfController)action(components[i]);// Then call the remaining, such as layout groups that change their children//taking their own RectTransform size into account.for (int i = 0; i < components.Count; i++)if (!(components[i] is ILayoutSelfController))action(components[i]);for (int i = 0; i < rect.childCount; i++)PerformLayoutControl(rect.GetChild(i) as RectTransform, action);}ListPool<Component>.Release(components);}

3.2 Graphic的Rebuild

Graphic元素:RawImage、Text、Image。

Graphic.cs中的SetDirty():

  • SetAllDirty()

  • SetVerticesDirty ()

  • SetMaterialDirty()

  • SetLayoutDirty()

Graphic.cs中的Rebuild()函数:

public virtual void Rebuild(CanvasUpdate update){if (canvasRenderer == null || canvasRenderer.cull)return;switch (update){case CanvasUpdate.PreRender:if (m_VertsDirty){UpdateGeometry();m_VertsDirty = false;}if (m_MaterialDirty){UpdateMaterial();m_MaterialDirty = false;}break;}}

当Graphic进行Rebuild时,UGUI将控制权转交给ICanvasElement接口的Rebuild方法。Graphic类实现了这个接口。

如果顶点数据已标记为Dirty状态(如组件的矩形变换更改大小时),则重建网格。 如果材质数据已标记为Dirty状态(如组件的材质或纹理发生改变时),则将更新附着的画布渲染器的材质。 Graphic的Rebuild不需要按特定顺序遍历Graphic组件列表,也不需要任何排序操作。

接下来:CanvasUpdateRegistry.RegisterCanvasElementForGraphicRebuild()→CanvasUpdateRegistry.InternalRegisterCanvasElementForGraphicRebuild→m_GraphicRebuildQueue.AddUnique

无论是 Layout,还是 Graphic 的改变,都会把本次的改变分别存储在对应的队列中,即m_LayoutRebuildQueue.AddUnique 和 m_GraphicRebuildQueue.AddUnique,殊途同归。

Graphic中SetDirty()调用的具体情况: Graphic的SetAllDirty(简称A)、SetVerticesDirty(简称V)、SetMaterialDirty(简称M)、SetLayoutDirty(简称L)

Graphic(Image、RawImage、Text的基类):OnEnable、Reset、OnDidApplyAnimationProperties、编辑器下OnValidate时调用A,OnRectTransformDimensionsChange调用V(如果不在重建layout则还会调用L),OnTransformParentChanged时调用A,设置Material 时调用M,设置Color 时调用V。

Image:Sprite改变时调用A,type、preserveAspect、fillCenter、fillMethod、fillAmount、fillClockwise、fillOrigin、useSpriteMesh改变时调用V,SetNativeSize时调用A,OnCanvasHierarchyChanged时调用V和L,OnDidApplyAnimationProperties时调用M和V。

RawImage: texture改变时调用V和M,uvRect改变时调用V,OnDidApplyAnimationProperties时调用V和M。

Text: FontTextureChanged、Font改变时调用A,text第一次写入时调用V(文本改变时还调用L)supportRichText、resizeTextForBestFit、resizeTextMinSize、resizeTextMaxSize、alignment、fontSize、horizontalOverflow、verticalOverflow、lineSpacing、fontStyle改变时调用V和L ,alignByGeometry改变时调用V。

BaseMeshEffect(Shadow的基类): OnEnable、OnDisable、OnDidApplyAnimationProperties、编辑器下OnValidate时调用V, (都是间接调用身上的Graphic的V,本身并不继承Graphic)

Shadow(Outline的基类): useGraphicAlpha、effectDistance、effectColor改变时调用V (都是间接调用Graphic的V,本身并不继承Graphic)

总结:OnEnable、OnDisable、OnTransformParentChanged、OnDidApplyAnimationProperties、OnRectTransformDimensionsChange、OnCanvasHierarchyChanged、图集加载完成时,Text、Image、RawImage、Shadow属性改变时。

3.3 Rebatch和Rebuild的触发条件总结

触发Rebatch的条件:

当Canvas下有Mesh发生改变时,如:

  • SetActive

  • Transform属性变化

  • Graphic的Color属性变化(改Mesh顶点色)

  • Text文本内容变化

  • Depth发生变化

触发Rebuild的条件:

  • Layout修改RectTransform部分影响布局的属性

  • Graphic的Mesh或Material发生变化

  • Mask裁剪内容变化

图源:Unity高锦锦Unity UGUI优化与原理【unity官方】_gaojinjingg的专栏-CSDN博客_unityugui优化

4 优化

转自UGUI性能优化总结 | 无境

4.1 动静分离

基于Rebatch是以Canvas为单位,当Canvas下UI元素发生变化时,会引起整个Canvas的重构,其中会包括网格合并,网格重叠检测,层级排序等操作。对于同一个界面,我们可以再细分Canvas,把相对静态的、不会变动的UI放在一个Canvas里,而相对变化比较频繁的UI就放在另一个Canvas里,使得频繁变化的Canvas里只对自己的Canvas下的元素进行Rebatch,而节省掉另一个Canvas中不需要变化的元素的Rebatch计算。

只有同一个Canvas下的UI元素才有可能合批,在中间新增Canvas会打断合批,动静分离优化本质是DrawCall换重构耗时的权衡。

Rebatch是在Canvas.BuildBatch函数中进行,而在Unity 5.2版本后,已经对Canvas.BuildBatch做了优化,优化后使用子线程进行计算,已经很大程度缓解了主线程的压力,目前来说动静分离并没有那么需要关注了。

4.2 其它

  • 慎用自带组件Outlien和Shaow,都是通过重复绘制多个Mesh实现的,其中Showdow绘制为原文本Mesh的2倍,而Outline为5倍,对渲染面数、顶点数,BuildBatch和SendWillRenderCanvases的耗时,Overdraw都有影响。若对于某种字体每次出现都需要这两种效果,可以让美术同学直接把阴影和描边做到字体里。

  • Text组件的Best Fit属性若非必要不要使用,它会使字号随着文本框大小而自动适配,一方面是适配本身在调整文本框大小时有CPU耗时开销,另一方面每个字号下新生成的字都会在Font Texture上占用一个字的大小,容易导致Font Texture过大(这个类似图集,当Font Texture当前大小放不下时才会占用更多内存)。这个特定问题已在 Unity 5.4 中得到纠正,Best Fit 不会不必要地扩展字体的纹理图集,但仍然比静态大小的文本慢得多。

  • 尽量少使用Layout组件,会增加Canvas.SendWillRenderCanvases函数耗时,利用好RectTransform同样可实现简单布局。

  • 对于血条、飘字、小地图标记等频繁更新位置的UI,可尽量减低更新频率,如隔帧更新,并设定更新阈值,当位移大于一定数值时再赋值(一方面是位移小时可能表现上看不出位移,另一方面是就算是没有实际位移,重复赋相同的值,也会SetDirty触发重建),可减少BuildBatch耗时。

参考:

[Unity官方]Optimizing Unity UI - Unity Learn

[UWA 学堂]影响性能更大的元凶Rebuild

Unity UGUI优化与原理【unity官方】_gaojinjingg的专栏-CSDN博客_unityugui优化

(五)UGUI源码分析之Rebuild(布局重建、图形重绘)_两水先木示的博客-CSDN博客_ugui重建

UGUI性能优化总结 | 无境

[UGUI源码剖析]—Rebuild 网格重建(画布刷新)系统相关推荐

  1. Unity3d:UGUI源码,Rebuild优化

    Image怎么绘制的 Unity中渲染的物体都是由网格(Mesh)构成的,而网格的绘制单元是图元(点.线.三角面) 绘制信息都存储在Vertexhelper类中,除了顶点外,还包括法线.UV.颜色.切 ...

  2. UGUI源码剖析(Image)

    Runtime类图分析 Image继承了MaskableGraphic, ISerializationCallbackReceiver, ILayoutElement, ICanvasRaycastF ...

  3. UGUI源码分析:GridLayoutGroup网格布局组件与ContentSizeFitter尺寸调节组件

    系列 UGUI源码分析系列总览 相关前置: UGUI CanvasUpdateSystem源码分析 UGUI源码分析:LayoutSystem布局系统 UGUI源码分析:LayoutGroup中的纵横 ...

  4. Unity中的UGUI源码解析之图形对象(Graphic)(2)-ICanvasElement

    Unity中的UGUI源码解析之图形对象(Graphic)(2)-ICanvasElement 在上一篇文章中, 我们对整个Graphic部分做了概述, 这篇文章我们介绍ICanvasElement和 ...

  5. ugui源码_UGUI 源码笔记(一)文件结构和部分组件使用

    这是我阅读 UGUI 源码记录的相关笔记,共三部分.文件结构和部分组件使用.输入事件.核心部分 ZeroyiQ:UGUI 源码笔记(一)文件结构和部分组件使用 ZeroyiQ:UGUI 源码笔记(二) ...

  6. UGUI源码分析:LayoutGroup中的纵横布局组件(HorizontalOrVerticalLayoutGroup)

    系列 UGUI源码分析系列总览 相关前置: UGUI CanvasUpdateSystem源码分析 UGUI源码分析:LayoutSystem布局系统 文章目录 系列 UML图一览 LayoutGro ...

  7. GDAL源码剖析(四)之命令行程序说明二

    接博客GDAL源码剖析(四)之命令行程序说明一http://blog.csdn.net/liminlu0314/article/details/6978589 其中有个nearblack,gdalbu ...

  8. UGUI源码分析:开关组件Toggle与ToggleGroup

    系列 UGUI源码分析系列总览 相关前置: UGUI EventSystem源码分析 UGUI源码分析:Selectable交互组件的基类 文章目录 系列 Toggle Toggle组件属性介绍 初始 ...

  9. K8s基础知识学习笔记及部分源码剖析

    K8s基础知识学习笔记及部分源码剖析 在学习b站黑马k8s视频资料的基础上,查阅了配套基础知识笔记和源码剖析,仅作个人学习和回顾使用. 参考资料: 概念 | Kubernetes 四层.七层负载均衡的 ...

最新文章

  1. 压缩aspx页面,移除aspx多余的空格 供学习参考
  2. mysqldatareader获取整行数据给datarow_C# sqladapter 与sqldataReader
  3. EXC_BAD_ACCESS调试
  4. java实现接收字符串对象并在后台代码中转成list对象
  5. python程序开发正则表达式_python正则表达式的使用(实验代码)
  6. P3345 [ZJOI2015]幻想乡战略游戏
  7. GUN/LINUX命令之 cp mv install
  8. 杨宏宇:腾讯多模态内容理解技术及应用
  9. echarts 3d饼图_echarts构建关系图,节点可收缩和展开,可添加点击事件
  10. Cisco路由器 VOIP 配置
  11. c语言指针地址交换程序,C语言-基础教程-指针的地址分配
  12. 设计导航网站|解决寻找合适的字体麻烦
  13. 树莓派 烧录arm64架构centos7
  14. 手写基础排序及查找算法
  15. 取消2008关机对话框
  16. Linux内核配置选项 (经典学习)
  17. 关于使用VBA调用AutoCAD的学习
  18. 大一python选择题题库及答案_python选择题库
  19. php 上传文件大小设置,调整PHP上传文件大小限制
  20. .Net Entity Framework Core 设置浮点数精度

热门文章

  1. 【爬虫+数据清洗+可视化分析】用Python分析哔哩哔哩“阳了“的评论数据
  2. JAVAWEB增删改查武林秘籍
  3. android 苹果开发进度,IOS 锁屏音乐信息显示(进度条,歌名等信息。)
  4. Delphi 鼠标模拟点击
  5. Xcode 常用编译选项设置
  6. 例如筋斗云的效果,但不通过offset定位的flag标记
  7. java getdate和getday,jq里面,如何用getDate()和getDay()函数遍历出当月的所有日子和星期?...
  8. Drag and drop拖放框架
  9. VS报错之混合模式程序集是针对“v1.1.4322”版的运行时生成的,在没有配置其他信息的情况下,无法在 4.0 运行时中加载该程序集。...
  10. 无法运行宏,可能是因为该宏在此工作簿中不可用,或者所有的宏都被禁用的解决方法...