第二章 渲染在哪里开始?

牢记,按第一章介绍的 npm start 启动本地调式环境才可进行调式

如果是 example 文件夹内的例子还需要 serve . 开启本地静态服务器

上一章介绍了 PixiJS 源码调式环境的安装,以及基本的调试方法。本章要研究一下它是如何渲染的

渲染大致步骤:

  1. 注册渲染器 renderer

  2. TickerPlugin 的 ticker 会自动开启并调用注册的回调函数 'TickerListener'

  3. 'TickerListener' 回调内调用 Application render 方法

  4. Application render 方法会调用渲染器 this.renderer.render(this.stage) 并传入 stage

  5. stage 是即是显示对像又是容器,所以只要渲染器开始调用 stage 的 render 方法,就会渲染 stage 下的所有子对象从而实现整颗显示对象树的渲染

还是以 example/simple.html 例子为例

<script type="text/javascript">
const app = new PIXI.Application({ width: 800, height: 600 });
document.body.appendChild(app.view); const sprite = PIXI.Sprite.from('logo.png');
sprite.x = 100;
sprite.y = 100;
sprite.anchor.set(0.5);
sprite.rotation = Math.PI / 4;
app.stage.addChild(sprite); app.ticker.add(() => {
sprite.rotation += 0.01;
});
</script>

sprite 是 Sprite 对象的实例, Sprite 实例继承自: Container -> DisplayObject -> EventEmitter

朔源至最顶层是 EventEmitter, 这是一个高性能事件库

EventEmitter https://github.com/primus/eventemitter3

至于为何它是高性能的,后面章节会顺便分析一下这个库

我们暂时不用去管这个 EventEmitter, 把它当做一个简单的事件收发库就行

先关注一下 DisplayObject,想要在画布中渲染,它必须得继承自 DisplayObject /packages/display/src/DisplayObject.ts

所有 DisplayObject 都继承自 EventEmitter, 可以监听事件, 触发事件

DisplayObject.ts 源码 210 行 可以看到它是一个抽象类

export abstract class DisplayObject extends utils.EventEmitter<DisplayObjectEvents>

以下显示对象都继承实现了这个抽象类

PIXI.Container
PIXI.Graphics
PIXI.Sprite
PIXI.Text
PIXI.BitmapText
PIXI.TilingSprite
PIXI.AnimatedSprite
PIXI.Mesh
PIXI.NineSlicePlane
PIXI.SimpleMesh
PIXI.SimplePlane
PIXI.SimpleRope

DisplayObject 有一个叫 render 的抽你方法需要子类实现

abstract render(renderer: Renderer): void;

render 方法就是各子类显示对像需要自己去实现绘制自己的方法

回到 example/simple.html 文件

app.stage 就是 Application 类的 stage 属性,它是一个 Container 对象,继承自 DisplayObject

stage 可以看作就是一棵显示对象树,而最顶层就是渲染方法就是 Application 的 render 方法

Application 实例化时它自身公开的 render 方法就被 TickerPlugin 插件的 init 方法调用了

/packages/ticker/TickerPlugin.ts 源码 68 行

ticker.add(this.render, this, UPDATE_PRIORITY.LOW); // 在ticker 内添加了 render() 回调

只要 ticker 开启,就会调用 Application 实例的 render 方法

/packages/app/src/Application.ts 第 70 - 90 行 构造函数与 render 方法

constructor(options?: Partial<IApplicationOptions>)
{
// The default options
options = Object.assign({
forceCanvas: false,
}, options); this.renderer = autoDetectRenderer<VIEW>(options);
// console.log('hello', 88888);
// install plugins here
Application._plugins.forEach((plugin) =>
{
plugin.init.call(this, options);
});
} /** Render the current stage. */
public render(): void
{
this.renderer.render(this.stage);
}

this.renderer 就是渲染器,把 this.stage 整个传到渲染器内渲染

往 stage 内添加子显示对象其实就是往一个 Container 内添加子显示对象,当然由于 Container 继承自 DisplayObject,所以 Container 也需要实现自己的 render 方法

/packages/display/src/Container.ts

render(renderer: Renderer): void
{
// 检测是否需要渲染
if (!this.visible || this.worldAlpha <= 0 || !this.renderable)
{
return;
} // 如果是特殊的对象需要特殊的渲染逻辑
if (this._mask || this.filters?.length)
{
this.renderAdvanced(renderer);
}
else if (this.cullable)
{
this._renderWithCulling(renderer);
}
else
{
this._render(renderer); for (let i = 0, j = this.children.length; i < j; ++i)
{
this.children[i].render(renderer);
}
}
}

这个 render 方法很简单,它接受一个 renderer 调用自己的 _render 后再遍历子显示对象调用子显示对象公开的 render 方法

就是一个显示对象树,从顶层开始调用往树了枝叶遍历调用 render 从而实现显示对象树的渲染

有一点需要注意,render 方法内显示它如果是一个 mask 遮罩或自带 filters 滤镜,那么需要调用更高极的渲染方法 renderAdvanced 或 _renderWithCulling,否则它先自己 this._render(renderer);

Container 本身自己的 _render 是空的,意味着它本身不会被渲染,只会被子显示对象渲染,但是继承实现它的子类,比如 Sprite,会去实现自己的 _render 方法覆盖实现渲染

renderer 渲染器

渲染器从哪里来的?

进入渲染器看看

渲染器是由 Application 类的构造函数内 autoDetectRenderer 判断返回的

渲染器类型分为三类:

export enum RENDERER_TYPE
{
/**
* Unknown render type.
* @default 0
*/
UNKNOWN,
/**
* WebGL render type.
* @default 1
*/
WEBGL,
/**
* Canvas render type.
* @default 2
*/
CANVAS,
}

我们找到 StartupSystem.ts 文件内的 defaultOptions 对象,将 hello 设为 true

static defaultOptions: StartupSystemOptions = {
/**
* {@link PIXI.IRendererOptions.hello}
* @default false
* @memberof PIXI.settings.RENDER_OPTIONS
*/
hello: true,
};

本地服务器下打开 example/simple.html, 浏览器控制台会输出

图 2-1

由输出的 PixiJS 7.3.2 - WebGL 2 可知,现在使用的是 WebGL 2

Renderer 类就是我们现在用到的渲染器 /packages/core/src/Renderer.ts

进入到 Renderer.ts 文件可以看到此类继承自 SystemManager 并实现了 IRenderer 接口

export class Renderer extends SystemManager<Renderer> implements IRenderer

进入构造函数:

/packages/core/src/Renderer.ts 第 292 - 364 行:

constructor(options?: Partial<IRendererOptions>)
{
super(); // Add the default render options
options = Object.assign({}, settings.RENDER_OPTIONS, options); this.gl = null; this.CONTEXT_UID = 0; this.globalUniforms = new UniformGroup({
projectionMatrix: new Matrix(),
}, true); const systemConfig = {
runners: [
'init',
'destroy',
'contextChange',
'resolutionChange',
'reset',
'update',
'postrender',
'prerender',
'resize'
],
systems: Renderer.__systems,
priority: [
'_view',
'textureGenerator',
'background',
'_plugin',
'startup',
// low level WebGL systems
'context',
'state',
'texture',
'buffer',
'geometry',
'framebuffer',
'transformFeedback',
// high level pixi specific rendering
'mask',
'scissor',
'stencil',
'projection',
'textureGC',
'filter',
'renderTexture',
'batch',
'objectRenderer',
'_multisample'
],
}; this.setup(systemConfig); if ('useContextAlpha' in options)
{
if (process.env.DEBUG)
{
// eslint-disable-next-line max-len
deprecation('7.0.0', 'options.useContextAlpha is deprecated, use options.premultipliedAlpha and options.backgroundAlpha instead');
}
options.premultipliedAlpha = options.useContextAlpha && options.useContextAlpha !== 'notMultiplied';
options.backgroundAlpha = options.useContextAlpha === false ? 1 : options.backgroundAlpha;
} this._plugin.rendererPlugins = Renderer.__plugins;
this.options = options as IRendererOptions;
this.startup.run(this.options);
}

Renderer 类内有一堆的 runners, plugins, systems

runners 即所谓的 signal '信号', 可以理解为 生命周期+状态变更时就会触发

plugins 即为 Renderer 所专门使用的插件

systems 即为 Renderer 所使用的系统,它由各个系统组合形成了渲染器 Renderer,以一辆车举例,'系统'可以理解组成车子的各个子系统,比如空调系统,油路系统,传动系统 等等

在构造函数中调用的 this.setup(systemConfig) 就是安装渲染函数所需要用到的系统,它来自 /packages/core/system/SystemManager.ts

进入 SystemManager.ts 找到 setup 方法:

setup(config: ISystemConfig<R>): void
{
this.addRunners(...config.runners); // Remove keys that aren't available
const priority = (config.priority ?? []).filter((key) => config.systems[key]); // Order the systems by priority
const orderByPriority = [
...priority,
...Object.keys(config.systems)
.filter((key) => !priority.includes(key))
]; for (const i of orderByPriority)
{
this.addSystem(config.systems[i], i);
}
console.log('看看runners里是什么:',this.runners)
}

可以看到,创建了很多个 Runner 对象存储在 this.runners 内

在 setup 函数最后一行打印看看 runners 里存了些啥

图 2-3

可以看到各个 Runner 对象的 items 里保存了所有的 system 当 Runner 被调用时,也即触发调用 items 内系统

找到 addSystem 方法:

addSystem(ClassRef: ISystemConstructor<R>, name: string): this
{
const system = new ClassRef(this as any as R); if ((this as any)[name])
{
throw new Error(`Whoops! The name "${name}" is already in use`);
} (this as any)[name] = system; this._systemsHash[name] = system; for (const i in this.runners)
{
this.runners[i].add(system);
} /**
* Fired after rendering finishes.
* @event PIXI.Renderer#postrender
*/ /**
* Fired before rendering starts.
* @event PIXI.Renderer#prerender
*/ /**
* Fired when the WebGL context is set.
* @event PIXI.Renderer#context
* @param {WebGLRenderingContext} gl - WebGL context.
*/ return this;
}

(this as any)[name] = system; 这一句就把 实例化后的 const system = new ClassRef(this as any as R); '系统' 按名称赋值到了 this 也即 Renderer 实例属性上了

所以通过 this.setup 后, 构造函数最后的 this.startup 属性 (StartupSystem) 可以访问,因为此时已经存在

根据注释,StartupSystem 就是用于负责初始化渲染器的,这是一切渲染的开始...

StartupSystem 的 run 方法 /packages/core/startup/StartupSystem.ts

第 56 - 69 行

run(options: StartupSystemOptions): void
{
const { renderer } = this;
console.log(renderer.runners.init)
renderer.runners.init.emit(renderer.options); if (options.hello)
{
// eslint-disable-next-line no-console
console.log(`PixiJS ${process.env.VERSION} - ${renderer.rendererLogId} - https://pixijs.com`);
} renderer.resize(renderer.screen.width, renderer.screen.height);
}

第 58 行输出 console.log(renderer.runners.init) 看看名为 init 的 Runner 属性 items 内有 6 个系统需要触发 emit

图 2-3

再看看 Runner 类 /packages/core/runner/Runner.ts

根据注释:Runner是一种高性能且简单的信号替代方案。最适合在事件以高频率分配给许多对象的情况下使用(比如每帧!)

注释中举的例子已经很清晰的说明了 Runner 的使用场景了

Runner 类似 Signal 模式:

import { Runner } from '@pixi/runner';

const myObject = {
loaded: new Runner('loaded'),
}; const listener = {
loaded: function() {
// Do something when loaded
}
}; myObject.loaded.add(listener); myObject.loaded.emit();

或用于处理多次调用相同函数

import { Runner } from '@pixi/runner';

const myGame = {
update: new Runner('update'),
}; const gameObject = {
update: function(time) {
// Update my gamey state
},
}; myGame.update.add(gameObject); myGame.update.emit(time);

Signal 和 观察者模式 之间的主要区别在于实现方式和使用场景。观察者模式通常涉及一个主题(Subject)和多个观察者(Observers),主题维护观察者列表并在状态变化时通知观察者。

观察者模式更加结构化,观察者需要显式地注册和注销,而且通常是一对多的关系。

相比之下,Signal 更加简单和灵活,它通常用于处理单个事件或消息的订阅和分发。

Signal 不需要维护观察者列表,而是直接将事件发送给所有订阅者。

Signal 更加轻量级,适用于简单的事件处理场景,而观察者模式更适合需要更多结构和控制的情况。

renderer 的 render 函数

渲染器 Renderer 类内调用的 render 是名为 objectRenderer 的 ObjectRendererSystem 对象

render(displayObject: IRenderableObject, options?: IRendererRenderOptions): void
{
this.objectRenderer.render(displayObject, options);
}

可以看到调用的是 ObjectRendererSystem 系统的 render 方法

/packages/core/src/render/ObjectRendererSystem.ts 第 49 - 125 行:

render(displayObject: IRenderableObject, options?: IRendererRenderOptions): void
{
const renderer = this.renderer; let renderTexture: RenderTexture;
let clear: boolean;
let transform: Matrix;
let skipUpdateTransform: boolean; if (options)
{
renderTexture = options.renderTexture;
clear = options.clear;
transform = options.transform;
skipUpdateTransform = options.skipUpdateTransform;
} // can be handy to know!
this.renderingToScreen = !renderTexture; renderer.runners.prerender.emit();
renderer.emit('prerender'); // apply a transform at a GPU level
renderer.projection.transform = transform; // no point rendering if our context has been blown up!
if (renderer.context.isLost)
{
return;
} if (!renderTexture)
{
this.lastObjectRendered = displayObject;
} if (!skipUpdateTransform)
{
// update the scene graph
const cacheParent = displayObject.enableTempParent(); displayObject.updateTransform();
displayObject.disableTempParent(cacheParent);
// displayObject.hitArea = //TODO add a temp hit area
} renderer.renderTexture.bind(renderTexture);
renderer.batch.currentRenderer.start(); if (clear ?? renderer.background.clearBeforeRender)
{
renderer.renderTexture.clear();
} displayObject.render(renderer); // apply transform..
renderer.batch.currentRenderer.flush(); if (renderTexture)
{
if (options.blit)
{
renderer.framebuffer.blit();
} renderTexture.baseTexture.update();
} renderer.runners.postrender.emit(); // reset transform after render
renderer.projection.transform = null; renderer.emit('postrender');
}

displayObject.updateTransform(); 这一句,会遍历显示对象树,计算所有显示对象的 localTransform 和 worldTransform ,这对于正常渲染元素的样子与位置至关重要

displayObject.render(renderer); 这一句,也就是传进来的 stage 对象,遍历子显示对象的 render 并将渲染器传入。

最终会调用到 Sprite 内的 _render 方法就是我们加入到 stage 的 'logo.png'

/packages/sprite/src/Sprite.ts 的第 369 - 375 行

图 2-4

batch 就是 BatchSystem 的实例

batch 的当前渲染器 ExtensionType.RendererPlugin

再调用 batch 渲染器的 render(this) 将 this 即当前 Sprite 对象传入

batch 批处理渲染器

batch 渲染器定义 /packages/core/batch/src/BatchRenderer.ts

由 BatchRenderer.ts 定义的 extension 可知它是一个 ExtensionType.RendererPlugin 类型的扩展插件

在源码最后一行 extensions.add(BatchRenderer); 可知,它默认就被安装(实例化)到了 Renderer 上

正是由于默认被实例化安装了,所以才能在 图 2-5 Sprite.ts 的 _render 函数中调用 renderer.plugins[this.pluginName].render(this);

让我们看看 BatchRenderer.ts 的 render 函数

/**
* Buffers the "batchable" object. It need not be rendered immediately.
* @param {PIXI.DisplayObject} element - the element to render when
* using this renderer
*/
render(element: IBatchableElement): void
{
if (!element._texture.valid)
{
return;
} if (this._vertexCount + (element.vertexData.length / 2) > this.size)
{
this.flush();
} this._vertexCount += element.vertexData.length / 2;
this._indexCount += element.indices.length;
this._bufferedTextures[this._bufferSize] = element._texture.baseTexture;
this._bufferedElements[this._bufferSize++] = element;
}

可以看到,这个 render 并不是立即渲染,而是将渲染数据缓存起来,等到渲染的时候再进行渲染。

由这个类的注释信息可知,它的作用是先缓存需要渲染的 texture 数据,等待将 多个 texture 信息直接提交到GPU进行批量渲染, 以减少 draw 次数提高性能

在这个 render 函数最后一行加一个 debugger 看看



图 2-5

/packages/core/src/render/ObjectRendererSystem.ts 的 render 函数, 也就是第 104 - 107 行:

displayObject.render(renderer);

        // apply transform..
renderer.batch.currentRenderer.flush();

等到 displayObject.render(renderer); 显示对像树遍历收集完渲染数据后才 flush 推到 GPU

进入 /packages/core/batch/src/BatchRenderer.ts 找到 flush 第 625 - 646 行:

flush(): void
{
if (this._vertexCount === 0)
{
return;
} this._attributeBuffer = this.getAttributeBuffer(this._vertexCount);
this._indexBuffer = this.getIndexBuffer(this._indexCount);
this._aIndex = 0;
this._iIndex = 0;
this._dcIndex = 0; this.buildTexturesAndDrawCalls();
this.updateGeometry();
this.drawBatches(); // reset elements buffer for the next flush
this._bufferSize = 0;
this._vertexCount = 0;
this._indexCount = 0;
}

至此 flush() 函数,才是真正调用 webgl 处

_attributeBuffer 是一个 ViewableBuffer 的实例对象

而随后的 this.buildTexturesAndDrawCalls(); 会调用 buildTexturesAndDrawCalls -> buildDrawCalls -> packInterleavedGeometry

/packages/core/batch/src/BatchRenderer.ts 766 - 800 行

packInterleavedGeometry(element: IBatchableElement, attributeBuffer: ViewableBuffer, indexBuffer: Uint16Array,
aIndex: number, iIndex: number): void
{
const {
uint32View,
float32View,
} = attributeBuffer; const packedVertices = aIndex / this.vertexSize;
const uvs = element.uvs;
const indicies = element.indices;
const vertexData = element.vertexData;
const textureId = element._texture.baseTexture._batchLocation; const alpha = Math.min(element.worldAlpha, 1.0);
const argb = Color.shared
.setValue(element._tintRGB)
.toPremultiplied(alpha, element._texture.baseTexture.alphaMode > 0); // lets not worry about tint! for now..
for (let i = 0; i < vertexData.length; i += 2)
{
float32View[aIndex++] = vertexData[i];
float32View[aIndex++] = vertexData[i + 1];
float32View[aIndex++] = uvs[i];
float32View[aIndex++] = uvs[i + 1];
uint32View[aIndex++] = argb;
float32View[aIndex++] = textureId;
} for (let i = 0; i < indicies.length; i++)
{
indexBuffer[iIndex++] = packedVertices + indicies[i];
}
}

packInterleavedGeometry 内会将 element.vertexData 顶点数据, uvs, argb 等信息存入 attributeBuffer

indexBuffer 是用来存储 sprite 渲染时所需的顶点索引的缓冲区。

在渲染 sprite 时,引擎需要知道如何连接顶点以形成正确的形状,而这些连接顶点的顺序就是通过 _indexBuffer 中的数据来定义的。

每三个索引对应一个顶点,通过这些索引,引擎可以正确地连接顶点以渲染出 sprite 的形状。

如果你把 indexBuffer 打印出来可以看到有 12 个值, WebGL 绘制几何体都是由三角形组成的

矩形由2个三角形组成

let vertices = [
0.5, 0.5, 0.0,
-0.5, 0.5, 0.0,
-0.5, -0.5, 0.0, // 第一个三角形
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0.5, 0.5, 0.0, // 第二个三角形
]; // 矩形

有一条边是公共,这个时候可以索引缓冲区对象减少冗余的数据

索引缓冲对象全称是 Index Buffer Object(IBO),通过索引的方式复用已有的数据。

顶点位置数据只需要 4 个就足够了,公共数据使用索引代替。

const vertices = [
0.5, 0.5, 0.0, // 第 1 个顶点
-0.5, 0.5, 0.0, // 第 2 个顶点
-0.5, -0.5, 0.0, // 第 3 个顶点
0.5, -0.5, 0.0, // 第 4 个顶点
]; // 矩形

绘制模式为 gl.TRIANGLES 时,两个三角形是独立的,索引数据如下:

const indexData = [

0, 1, 2, // 对应顶点位置数据中 1、2、3 顶点的索引

0, 2, 3, // 对应顶点位置数据中 1、3、4 顶点的索引

]

这就是为什么Sprite.ts 类中 const indices = new Uint16Array([0, 1, 2, 0, 2, 3]); 如此定义的原因

相关知识可参考: https://segmentfault.com/a/1190000041144928

接下来是 this.updateGeometry(); 简单来说它它会创建几何模型 和 shader

最后调用 this.drawBatches() 内调用 gl.drawElements() 将前面缓存整理好的 buffer 绘制到 GPU

不管是 Canvas context 还是 WebGL 都是非对象的过程式的调用,PixiJS 的 Renderer 封装了这些操作,让开发者更专注于业务逻辑。

将过程式的调用封装成对象

WebGL 想要渲染,原理:

顶点着色器 + 片段着色器, 顶点着色器确定顶点位置,片段着色器确定每个片元的像素颜色

组成的着色程序 program 后通过 gl.drawArrays 或 gl.drawElements 运行一个着色方法对绘制到 GPU 上

我们采取先整体再细节的方式阅读源码,WebGL 具体渲染挺复杂的,暂时可以略过,如果有兴趣可以参考 WebGL 教程

这是一个很好的 WebGL 教程 https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-fundamentals.html

本章小节

本章通过分析 webgl 渲染器,顺带看了部分 PixiJS 的 system/SystemManager "系统设计", 咋一看确实很复杂

优秀的设计时分值得借鉴,完全可以运用到自己的项目或组件库内

我对 webgl 了解的十分粗浅但借助 debugger 还是可以一步一步分析出逻辑走向,道阻且长啊

最新的 PixiJS 已经支持 WebGPU 渲染了,学不动了...


注:转载请注明出处博客园:王二狗Sheldon池中物 (willian12345@126.com)

PixiJS源码分析系列:第二章 渲染在哪里开始?的更多相关文章

  1. Thinkphp源码分析系列–开篇

    目前国内比较流行的php框架由thinkphp,yii,Zend Framework,CodeIgniter等.一直觉得自己在php方面还是一个小学生,只会用别人的框架,自己也没有写过,当然不是自己不 ...

  2. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  3. Spring IOC 容器源码分析系列文章导读

    1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本.经过十几年的迭代,现在的 Spring 框架已经非常成熟了.Spring ...

  4. Bootstrap源码分析系列之初始化和依赖项

    在上一节中我们介绍了Bootstrap整体架构,本节我们将介绍Bootstrap框架第二部分初始化及依赖项,这部分内容位于源码的第8~885行,打开源码这部分内容似乎也不是很难理解.但是请站在一个开发 ...

  5. Netty 源码分析系列(二)Netty 架构设计

    前言 上一篇文章,我们对 Netty做了一个基本的概述,知道什么是Netty以及Netty的简单应用. Netty 源码分析系列(一)Netty 概述 本篇文章我们就来说说Netty的架构设计,解密高 ...

  6. Spring Ioc源码分析系列--Ioc容器BeanFactoryPostProcessor后置处理器分析

    Spring Ioc源码分析系列--Ioc容器BeanFactoryPostProcessor后置处理器分析 前言 上一篇文章Spring Ioc源码分析系列--Ioc源码入口分析已经介绍到Ioc容器 ...

  7. Spring Ioc源码分析系列--Bean实例化过程(一)

    Spring Ioc源码分析系列--Bean实例化过程(一) 前言 上一篇文章Spring Ioc源码分析系列--Ioc容器注册BeanPostProcessor后置处理器以及事件消息处理已经完成了对 ...

  8. Spring Ioc源码分析系列--容器实例化Bean的四种方法

    Spring Ioc源码分析系列--实例化Bean的几种方法 前言 前面的文章Spring Ioc源码分析系列--Bean实例化过程(二)在讲解到bean真正通过那些方式实例化出来的时候,并没有继续分 ...

  9. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  10. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

随机推荐

  1. pod(三):pod的管理

    目录 一.系统环境 二.前言 三.pod的管理 3.1 环境介绍 3.2 管理pod 一.系统环境 服务器版本 docker软件版本 CPU架构 CentOS Linux release 7.4.17 ...

  2. 电脑临时文件清理2个方法?%temp% cleanmgr

    按住电脑快捷键win+R,打开运行框 输入代码 %temp%,点击回车enter或者点击确定,打开temp文件夹[此处存放的都是系统无用的缓存垃圾] 按快捷键Ctrl + A ,点击delete,删除 ...

  3. MLP实现波士顿房屋价格回归任务

    1. 数据集 波士顿房屋价格.csv文件,文件中的数据有可能不完整,部分数据如下: CRIM, ZN ,INDUS ,CHAS,NOX,RM,AGE,DIS,RAD,TAX,PTRATIO,LSTAT ...

  4. C# xml与对象相互转换

    例如: 1.对象转xml(对象序列化为xml) string strImage= XmlSerializeHelper.Serialize<List<ImageSingle>> ...

  5. 【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Windows)

    在使用App Service服务部署业务应用,因为有些第三方的接口需要调用者携带TLS/SSL证书(X509 Certificate),在官方文档中介绍了两种方式在代码中使用证书: 1) 直接使用证书 ...

  6. 8.10考试总结(NOIP模拟35)[玩游戏·排列·最短路·矩形]

    所谓人,无论是谁到了最后,都会形单影只. T1 玩游戏 解题思路 可以把序列从 k 位置掰成两个序列. 问题就变成了两个序列从开头走向末尾是否可以保证前缀和之和一直不大于 0 . 并且可以移动到两个序 ...

  7. drawio中添加数学公式

    1.drawio简介 drawio是一款免费开源的流程图绘制软件,由于软件免费,而且模块也很丰富,我比较喜欢用它. 软件下载地址:https://github.com/jgraph/drawio-de ...

  8. 还在拼冗长的WhereIf吗?100行代码解放这个操作

    通常我们在做一些数据过滤的操作的时候,经常需要做一些判断再进行是否要对其进行条件过滤. 普通做法 最原始的做法我们是先通过If()判断是否需要进行数据过滤,然后再对数据源使用Where来过滤数据. 示 ...

  9. 在 TypeScript 中,extends

    extends 是一个关键字,用于指定类型参数的约束.它在类型参数的声明中使用,以确保类型参数满足特定的条件. 具体来说,extends 后面可以跟随一个类型,表示类型参数必须是该类型的子类型.在泛型 ...

  10. LeetCode 128. Longest Consecutive Sequence 最长连续序列 (C++/Java)

    题目: Given an unsorted array of integers, find the length of the longest consecutive elements sequenc ...