第二章 渲染在哪里开始?

牢记,按第一章介绍的 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. IDS4 傻瓜式实践指南

    前言: 这是一篇实践指南,不会过多的解释原理(因为我也说不清楚,想了解的同学请移步老张的博客,里面有非常详细的介绍),本篇文章讲解如何简单的使用IDS4来实现单点登录,以及遇到的一些坑实现功能: 1. ...

  2. objectarx 之让用户自定义插件命令

    #include <iostream> #include <fstream> virtual AcRx::AppRetCode On_kInitAppMsg (void *pk ...

  3. 宝塔面板6.X在Docker中安装宝塔面板5.9.1 – 我是不是太闲了?

    我是不是太闲了,问你们三遍,场景是这样的,我在一台VPS里面安装宝塔面板6.X,宝塔面板的软件商店有Docker管理器2.0这一个免费好用的小玩意.大鸟安装好Docker管理器2.0,然后在Docke ...

  4. Flutter(九)Json序列化与反序列化(转Model)

    在日常开发中JSON的序列化与反序列化是一个常见的操作:而Dart语言不支持反射,运行时反射会影响Dart的tree shaking(摇树优化),tree shaking可以"抖掉" ...

  5. 开源云原生平台对比 KubeSphere vs Rainbond

    最近因为工作需要,需要找一个功能完善的云原生应用平台,经过自己筛选和朋友推荐,剩下 KubeSphere和Rainbond ,这两个产品都是基于 Kubernetes 之上构建的云原生应用平台,功能都 ...

  6. itest work(爱测试) 开源一站式接口测试&敏捷测试工作站 9.0.5. Rc4

    (一)itest work 简介 itest work (爱测试)  一站式工作站让测试变得简单.敏捷,"好用.好看,好敏捷" ,是itest wrok 追求的目标.itest w ...

  7. python实现取得成员所在的多个位置

    注:本代码主要是为了实现多个集合之间求并集时的辅助代码,简单的举个例子来说明代码的功能. 约定:例如{11: [2, 3]}表示数据11在集合2和集合3中都存在. 现有以下数据: d0 = {38:  ...

  8. vant做城市列表

    vant: https://youzan.github.io/vant/#/zh-CN/ 安装 cnpm i -S vant 按需加载配置 # 在 babel.config.js 中配置 module ...

  9. INFINI Gateway 如何防止大跨度查询

    背景 业务每天生成一个日期后缀的索引,写入当日数据. 业务查询有时会查询好多天的数据,导致负载告警. 现在想对查询进行限制--只允许查询一天的数据(不限定是哪天),如果想查询多天的数据就走申请. 技术 ...

  10. 利用Wireshark抓包分析DNS域名解析过程

    一.DNS协议概述   DNS协议也可以称为DNS服务,全称是Domain Name System,即域名系统,和HTTP协议一样,也是一个位于应用层的协议(服务),它是基于运输层的UDP协议的.从D ...