在项目里面有一个需求,将屏幕通过水平线分割为一个个Channel,并通过Allocator基于某一个已分配的段进行新段分配,而为了提升自己的代码设计能力和拓展性的要求,我将这个功能做成了一个Allocator功能库。
项目中只需要对线性空间进行分配,因此实现时只考虑了线性可分配区域。
首先我将每个Channel称为AllocatableRegion(以下简称Region),每个 Region都知晓其邻近Region,并拥有一个RegionId,而每一段已分配的空间被称为AllocatedSpace。每个Region都拥有一个TryOcuppy函数,参数为数轴上一段距离而返回null或者AllocatedSpace。用户只通过管理所有Region的Allocator申请空间。而申请空间时的策略通过在创建Allocator时传入一个AllocateStrategy数组指定(TODO:更改为Hub形式,但是需要考虑性能问题)。
Allocator功能库包含以下类:
- Sequence:仅代表数轴上某起始点到终点的一段距离。
- AllocatedSpace:代表某Region上已分配的一段空间。
- AllocatableRegion:代表某一个可分配区域,相邻Region通过链表指针相连。
- RegionFactory:Region生成的工厂方法。
- Allocator:用户直接操作的分配器对象。
- AllocateTask:在Allocator工具库内部使用的上下文类。
- AllocateStrategyPayload:在AllocateStrategy中使用的上下文类,成员包含AllocateTask。
- AllocateStrategyBook:AllocateStrategy数组和策略执行类。
- AllocateStrategy:分配策略。
Sequence类
sequence类表明数轴上某一段线段,同时包含比较函数和冲突检测函数。
class Sequence { from: number; to: number; constructor(from: number = 0, to: number = 0) { this.from = from; this.to = to; } compare(seq: Sequence) { return this.conflict(seq) ? 0 : this.to <= seq.from ? -1 : 1; } conflict(seq: Sequence) { return !( this.from != seq.from && (this.from >= seq.to || this.to <= seq.from) ); } }
使用:
let firstSeg = new Sequence(0,100); let conflictSeg = new Sequence(50,200); let thirdSeg = new Sequence(200,300); console.log(firstSeg.conflict(conflictSeg))//true console.log(firstSeg.conflict(thirdSeg ))//false
AllocatableRegion类
AllocatableRegion类是整个Allocator工具库的核心数据存储类,其中包含该区域所有已分配块的链表。
class AllocatableRegion<T> { id: number = 0; allocatedSpaceNum: number = 0; next: AllocatableRegion<T> | null = null; previous: AllocatableRegion<T> | null = null; allocator: Allocator<T>; head: AllocatedSpace<T> | null = null; tail: AllocatedSpace<T> | null = null; constructor( allocator: Allocator<T>, addEntityToRegion: (entity: T) => void ) { this.allocator = allocator; this.addEntityToRegion = addEntityToRegion; } addEntityToRegion: (entity: T) => void; tryOccupy(req: AllocateTask<T>): AllocatedSpace<T> | null { if (!this.head) { this.head = new AllocatedSpace(req.seq, req.entity, this); this.tail = this.head; this.allocatedSpaceNum++; this.addEntityToRegion(req.entity); return this.head; } let space = null; let node: AllocatedSpace<T> | null = this.head; let res = 0; while (node) { // res = node.seq.compare(req.seq); if (res == 0) return null; // req -- node let cmp; if (res > 0) { if (node.previous) { cmp = node.previous.seq.compare(req.seq); if (cmp < 0) { space = new AllocatedSpace(req.seq, req.entity, this); space.next = node; space.previous = node.previous; node.previous.next = space; node.previous = space; this.allocatedSpaceNum++; this.addEntityToRegion(req.entity); return space; } else if (cmp == 0) { return null; } node = node.next; continue; } else { space = new AllocatedSpace(req.seq, req.entity, this); node.previous = space; space.next = node; this.head = space; this.allocatedSpaceNum++; this.addEntityToRegion(req.entity); return space; } } // node -- req else { if (node.next) { cmp = node.next.seq.compare(req.seq); if (cmp > 0) { space = new AllocatedSpace(req.seq, req.entity, this); space.previous = node; space.next = node.next; node.next.previous = space; node.next = space; this.allocatedSpaceNum++; this.addEntityToRegion(req.entity); return space; } else if (cmp == 0) { return null; } node = node.next; continue; } else { space = new AllocatedSpace(req.seq, req.entity, this); node.next = space; space.previous = node; this.tail = space; this.allocatedSpaceNum++; this.addEntityToRegion(req.entity); return space; } } } return null; } tryRelease(space: AllocatedSpace<T>): boolean { if (space.previous) { space.previous.next = space.next; } else { this.head = space.next; } if (space.next) { space.next.previous = space.previous; } else { this.tail = space.previous; } return true; } } export class RegionFactory<T> { private creator: { new ( allocator: Allocator<T>, addEntityToChannel: (entity: T) => void ): AllocatableRegion<T>; }; addTweakFunc: (entity: T) => void; //@ts-ignore allocator: Allocator<T>; constructor( Creator: { new ( allocator: Allocator<T>, addEntityToChannel: (entity: T) => void ): AllocatableRegion<T>; }, addTweakFunc: (entity: T) => void ) { this.creator = Creator; this.addTweakFunc = addTweakFunc; } create(id: number, allocator?: Allocator<T>): AllocatableRegion<T>; create( id: number, previous: AllocatableRegion<T> | null, next: AllocatableRegion<T> | null, allocator?: Allocator<T> ): AllocatableRegion<T>; create( id: number, x?: AllocatableRegion<T> | Allocator<T> | null, y?: AllocatableRegion<T> | null, z?: Allocator<T> ): AllocatableRegion<T> { let region; switch (arguments.length) { //@ts-ignore case 4: case 3: x = x as AllocatableRegion<T>; y = y as AllocatableRegion<T>; region = new this.creator(z ?? this.allocator, this.addTweakFunc); region.id = id; if (x != null) x.next = x; if (y != null) y.previous = region; region.previous = x; region.next = y; return region; case 1: case 2: default: x = x as Allocator<T>; region = new this.creator(x ?? this.allocator, this.addTweakFunc); region.id = id; return region; } } }
核心方法为tryOccupy(req: AllocateTask<T>): AllocatedSpace<T> | null,调用该方法会尝试占据AllocateTask中标明的区域,成功则返回AllocatedSpace没有成功则返回null,该方法由Allocator调用。其中在占有区域成功时会调用一个用户回调函数addEntityToRegion,用户可在该函数中对传入的数据实体对象进行操作。
RegionFactory为Region的工厂方法,通过该类实例化新Region。
AllocatedSpace 类
AllocatedSpace类用于表明区域某一已分配段,会作为分配成功时的返回值。该类实现接口AllocateTask,是AllocatableRegion中已分类段链表的基本项。
class AllocateTask<T> { allocator: Allocator<T>; seq: Sequence; type: number; entity: T; base?: AllocatedSpace<T>; constructor( entity: T, seq: Sequence, allocator: Allocator<T>, type: number = 0, base?: AllocatedSpace<T> ) { this.entity = entity; this.seq = seq; this.allocator = allocator; this.type = type; this.base = base; } } class AllocatedSpace<T> extends AllocateTask<T> { seq: Sequence; entity: T; region: AllocatableRegion<T>; reusable: boolean = false; type: number; allocator: Allocator<T>; previous: AllocatedSpace<T> | null = null; next: AllocatedSpace<T> | null = null; constructor(seq: Sequence, entity: T, region: AllocatableRegion<T>) { this.seq = seq; this.entity = entity; this.region = region; this.allocator = this.region.allocator; } safeRemove() { if (this.previous) { this.previous.next = this.next; } else { this.region.head = this.next; } if (this.next) { this.next.previous = this.previous; } else { this.region.tail = this.previous; } this.region.allocatedSpaceNum--; } }
AllocateStrategy类
分配策略AllocateStrategy类可以看成一个独立的系统,实例化Allocator时需要传入的为分配策略书AllocateStrategyBook,该类包含分配策略列表和执行函数,而AllocateStrategy需要用户传入judeFn和自定义data,每次执行策略时, AllocateStrategyBook会通过数据负载AllocateStrategyPayload类中result值进行判断下一个需要执行的策略(TODO:具名策略,{tryOcuppyBaseChannel:…},trySecondStrategy:{….}}这样也可以有代码提示),只有result为NaN时,才判断为分配成功。
class AllocateStrategy<T> { data?: any; judge: (pyload: AllocateStrategyPayload<T>) => AllocateStrategyPayload<T>; constructor( judgeFn: (pyload: AllocateStrategyPayload<T>) => AllocateStrategyPayload<T>, data?: any ) { this.judge = judgeFn; this.data = data; } } class AllocateStrategyBook<T> { strategies: AllocateStrategy<T>[] = []; execute(task: AllocateTask<T>): AllocateStrategyPayload<T> { let payload = new AllocateStrategyPayload<T>(task); let missionId = 0; let missionLength = this.strategies.length; while (missionLength > missionId) { payload = this.strategies[payload.to()].judge(payload); if (isNaN(payload.result) || payload.result < 0) { return payload; } } return payload; } } class AllocateStrategyPayload<T> { task: AllocateTask<T>; result: number = 0; private _to: number = 0; next(id?: number) { return (this._to = id ?? this._to + 1); } to() { return this._to; } constructor(task: AllocateTask<T>) { this.task = task; this._to = task.type; } }
分配策略示例:
下面tryOccupyBaseChannel为尝试占有前一个AllocatedSpace相同Region的段,如果无法占有段则payload.next()执行下一条策略。
const tryOcuppyBaseChannel: PlotAllocateStrategy = { judge: (payload) => { let task = payload.task; if (!task.base) { if (!payload.task.allocator.regions.next) { let res = task.allocator.regions.tryOccupy(task); if (res) { payload.task = res; payload.result = NaN; return payload; } } payload.next(); return payload; } let res = task.base.region.tryOccupy(task); if (!res) { payload.next(); return payload; } payload.result = NaN; payload.task = res; return payload; }, };
Allocator类
用户通过Allocator类操作分配区域。用户使用allocate方法申请分配空间。
export class Allocator<T> { regions: AllocatableRegion<T>; allocateStrategy: AllocateStrategyBook<T>; regionFactory: RegionFactory<T>; releaseStrategy: AllocateStrategyBook<T>; constructor( allocateStrategy: AllocateStrategyBook<T>, releaseStrategy: AllocateStrategyBook<T>, regionFactory: RegionFactory<T> ) { this.allocateStrategy = allocateStrategy; this.releaseStrategy = releaseStrategy; this.regionFactory = regionFactory; this.regions = this.regionFactory.create(0); } allocate( from: number, to: number, entity: T, base?: AllocatedSpace<T>, type?:number ): AllocatedSpace<T> | null; allocate( task: AllocateTask<T> | number, to: number, entity: T, base?: AllocatedSpace<T>, type?:number ): AllocatedSpace<T> | null { if (typeof task == 'number') { task = new AllocateTask(entity, new Sequence(task, to), this, type, base); } let res = this.allocateStrategy.execute(task); if (res.result < 0) { return null; } return res.task as AllocatedSpace<T>; } release(space: AllocatedSpace<T>): boolean { let res = this.releaseStrategy.execute(space); return res.result >= 0; } }
整体架构
类图关系比较复杂,有空再画。
v1.0 wep 2022/01/13 文章基本内容