升级项目框架,移除大部分无用的物理和tween系统
This commit is contained in:
@@ -1,234 +0,0 @@
|
||||
namespace es {
|
||||
/**
|
||||
* 记录时间的持续时间,一些设计灵感来自物理秒表。
|
||||
*/
|
||||
export class Stopwatch {
|
||||
/**
|
||||
* 秒表启动的系统时间。
|
||||
* undefined,如果秒表尚未启动,或已复位。
|
||||
*/
|
||||
private _startSystemTime: number | undefined;
|
||||
/**
|
||||
* 秒表停止的系统时间。
|
||||
* undefined,如果秒表目前没有停止,尚未开始,或已复位。
|
||||
*/
|
||||
private _stopSystemTime: number | undefined;
|
||||
/** 自上次复位以来,秒表已停止的系统时间总数。 */
|
||||
private _stopDuration: number = 0;
|
||||
/**
|
||||
* 用秒表计时,当前等待的切片开始的时间。
|
||||
* undefined,如果秒表尚未启动,或已复位。
|
||||
*/
|
||||
private _pendingSliceStartStopwatchTime: number | undefined;
|
||||
/**
|
||||
* 记录自上次复位以来所有已完成切片的结果。
|
||||
*/
|
||||
private _completeSlices: Slice[] = [];
|
||||
|
||||
constructor(private readonly getSystemTime = _defaultSystemTimeGetter) {
|
||||
}
|
||||
|
||||
public getState() {
|
||||
if (this._startSystemTime === undefined) {
|
||||
return State.IDLE;
|
||||
} else if (this._stopSystemTime === undefined) {
|
||||
return State.RUNNING;
|
||||
} else {
|
||||
return State.STOPPED;
|
||||
}
|
||||
}
|
||||
|
||||
public isIdle() {
|
||||
return this.getState() === State.IDLE;
|
||||
}
|
||||
|
||||
public isRunning() {
|
||||
return this.getState() === State.RUNNING;
|
||||
}
|
||||
|
||||
public isStopped() {
|
||||
return this.getState() === State.STOPPED;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public slice() {
|
||||
return this.recordPendingSlice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自上次复位以来该秒表已完成/记录的所有片的列表。
|
||||
*/
|
||||
public getCompletedSlices(): Slice[] {
|
||||
return Array.from(this._completeSlices);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取自上次重置以来该秒表已完成/记录的所有片的列表,以及当前挂起的片。
|
||||
*/
|
||||
public getCompletedAndPendingSlices(): Slice[] {
|
||||
return [...this._completeSlices, this.getPendingSlice()];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关于这个秒表当前挂起的切片的详细信息。
|
||||
*/
|
||||
public getPendingSlice(): Slice {
|
||||
return this.calculatePendingSlice();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前秒表时间。这是这个秒表自上次复位以来运行的系统时间总数。
|
||||
*/
|
||||
public getTime() {
|
||||
return this.caculateStopwatchTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 完全重置这个秒表到它的初始状态。清除所有记录的运行持续时间、切片等。
|
||||
*/
|
||||
public reset() {
|
||||
this._startSystemTime = this._pendingSliceStartStopwatchTime = this._stopSystemTime = undefined;
|
||||
this._stopDuration = 0;
|
||||
this._completeSlices = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始(或继续)运行秒表。
|
||||
* @param forceReset
|
||||
*/
|
||||
public start(forceReset: boolean = false) {
|
||||
if (forceReset) {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
if (this._stopSystemTime !== undefined) {
|
||||
const systemNow = this.getSystemTime();
|
||||
const stopDuration = systemNow - this._stopSystemTime;
|
||||
|
||||
this._stopDuration += stopDuration;
|
||||
this._stopSystemTime = undefined;
|
||||
} else if (this._startSystemTime === undefined) {
|
||||
const systemNow = this.getSystemTime();
|
||||
this._startSystemTime = systemNow;
|
||||
this._pendingSliceStartStopwatchTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param recordPendingSlice
|
||||
*/
|
||||
public stop(recordPendingSlice: boolean = false) {
|
||||
if (this._startSystemTime === undefined) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const systemTimeOfStopwatchTime = this.getSystemTimeOfCurrentStopwatchTime();
|
||||
|
||||
if (recordPendingSlice) {
|
||||
this.recordPendingSlice(this.caculateStopwatchTime(systemTimeOfStopwatchTime));
|
||||
}
|
||||
|
||||
this._stopSystemTime = systemTimeOfStopwatchTime;
|
||||
return this.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指定秒表时间的当前挂起片。
|
||||
* @param endStopwatchTime
|
||||
*/
|
||||
private calculatePendingSlice(endStopwatchTime?: number): Slice {
|
||||
if (this._pendingSliceStartStopwatchTime === undefined) {
|
||||
return Object.freeze({ startTime: 0, endTime: 0, duration: 0 });
|
||||
}
|
||||
|
||||
if (endStopwatchTime === undefined) {
|
||||
endStopwatchTime = this.getTime();
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
startTime: this._pendingSliceStartStopwatchTime,
|
||||
endTime: endStopwatchTime,
|
||||
duration: endStopwatchTime - this._pendingSliceStartStopwatchTime
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算指定系统时间的当前秒表时间。
|
||||
* @param endSystemTime
|
||||
*/
|
||||
private caculateStopwatchTime(endSystemTime?: number) {
|
||||
if (this._startSystemTime === undefined)
|
||||
return 0;
|
||||
|
||||
if (endSystemTime === undefined)
|
||||
endSystemTime = this.getSystemTimeOfCurrentStopwatchTime();
|
||||
|
||||
return endSystemTime - this._startSystemTime - this._stopDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取与当前秒表时间等效的系统时间。
|
||||
* 如果该秒表当前停止,则返回该秒表停止时的系统时间。
|
||||
*/
|
||||
private getSystemTimeOfCurrentStopwatchTime() {
|
||||
return this._stopSystemTime === undefined ? this.getSystemTime() : this._stopSystemTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束/记录当前挂起的片的私有实现。
|
||||
* @param endStopwatchTime
|
||||
*/
|
||||
private recordPendingSlice(endStopwatchTime?: number) {
|
||||
if (this._pendingSliceStartStopwatchTime !== undefined) {
|
||||
if (endStopwatchTime === undefined) {
|
||||
endStopwatchTime = this.getTime();
|
||||
}
|
||||
|
||||
const slice = this.calculatePendingSlice(endStopwatchTime);
|
||||
|
||||
this._pendingSliceStartStopwatchTime = slice.endTime;
|
||||
this._completeSlices.push(slice);
|
||||
return slice;
|
||||
} else {
|
||||
return this.calculatePendingSlice();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回某个系统的“当前时间”的函数。
|
||||
* 惟一的要求是,对该函数的每次调用都必须返回一个大于或等于前一次对该函数的调用的数字。
|
||||
*/
|
||||
export type GetTimeFunc = () => number;
|
||||
|
||||
enum State {
|
||||
/** 秒表尚未启动,或已复位。 */
|
||||
IDLE = "IDLE",
|
||||
/** 秒表正在运行。 */
|
||||
RUNNING = "RUNNING",
|
||||
/** 秒表以前还在跑,但现在已经停了。 */
|
||||
STOPPED = "STOPPED"
|
||||
}
|
||||
|
||||
export function setDefaultSystemTimeGetter(systemTimeGetter: GetTimeFunc = Date.now) {
|
||||
_defaultSystemTimeGetter = systemTimeGetter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 由秒表记录的单个“薄片”的测量值
|
||||
*/
|
||||
interface Slice {
|
||||
/** 秒表显示的时间在这一片开始的时候。 */
|
||||
readonly startTime: number;
|
||||
/** 秒表在这片片尾的时间。 */
|
||||
readonly endTime: number;
|
||||
/** 该切片的运行时间 */
|
||||
readonly duration: number;
|
||||
}
|
||||
|
||||
/** 所有新实例的默认“getSystemTime”实现 */
|
||||
let _defaultSystemTimeGetter: GetTimeFunc = Date.now;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
module es {
|
||||
export interface IAnimFrame {
|
||||
t: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export class AnimCurve {
|
||||
public get points(): IAnimFrame[] {
|
||||
return this._points;
|
||||
}
|
||||
|
||||
public constructor(points: IAnimFrame[]) {
|
||||
if (points.length < 2) {
|
||||
throw new Error('curve length must be >= 2');
|
||||
}
|
||||
points.sort((a: IAnimFrame, b: IAnimFrame) => {
|
||||
return a.t - b.t;
|
||||
});
|
||||
if (points[0].t !== 0) {
|
||||
throw new Error('curve must start with 0');
|
||||
}
|
||||
if (points[points.length - 1].t !== 1) {
|
||||
throw new Error('curve must end with 1');
|
||||
}
|
||||
this._points = points;
|
||||
}
|
||||
|
||||
public lerp(t: number): number {
|
||||
for (let i = 1; i < this._points.length; i++) {
|
||||
if (t <= this._points[i].t) {
|
||||
const m = MathHelper.map01(
|
||||
t,
|
||||
this._points[i - 1].t,
|
||||
this._points[i].t
|
||||
);
|
||||
return MathHelper.lerp(
|
||||
this._points[i - 1].value,
|
||||
this._points[i].value,
|
||||
m
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new Error('should never be here');
|
||||
}
|
||||
|
||||
public _points: IAnimFrame[];
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
module es {
|
||||
export class Bag<E> implements ImmutableBag<E> {
|
||||
public size_ = 0;
|
||||
public length = 0;
|
||||
private array: Array<E> = [];
|
||||
|
||||
constructor(capacity = 64) {
|
||||
this.length = capacity;
|
||||
}
|
||||
|
||||
removeAt(index: number): E {
|
||||
const e: E = this.array[index];
|
||||
this.array[index] = this.array[--this.size_];
|
||||
this.array[this.size_] = null;
|
||||
return e;
|
||||
}
|
||||
|
||||
remove(e: E): boolean {
|
||||
let i: number;
|
||||
let e2: E;
|
||||
const size = this.size_;
|
||||
|
||||
for (i = 0; i < size; i++) {
|
||||
e2 = this.array[i];
|
||||
|
||||
if (e == e2) {
|
||||
this.array[i] = this.array[--this.size_];
|
||||
this.array[this.size_] = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
removeLast(): E {
|
||||
if (this.size_ > 0) {
|
||||
const e: E = this.array[--this.size_];
|
||||
this.array[this.size_] = null;
|
||||
return e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
contains(e: E): boolean {
|
||||
let i: number;
|
||||
let size: number;
|
||||
|
||||
for (i = 0, size = this.size_; size > i; i++) {
|
||||
if (e === this.array[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
removeAll(bag: ImmutableBag<E>): boolean {
|
||||
let modified = false;
|
||||
let i: number;
|
||||
let j: number;
|
||||
let l: number;
|
||||
let e1: E;
|
||||
let e2: E;
|
||||
|
||||
for (i = 0, l = bag.size(); i < l; i++) {
|
||||
e1 = bag[i];
|
||||
|
||||
for (j = 0; j < this.size_; j++) {
|
||||
e2 = this.array[j];
|
||||
|
||||
if (e1 === e2) {
|
||||
this.removeAt(j);
|
||||
j--;
|
||||
modified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
get(index: number): E {
|
||||
if (index >= this.length) {
|
||||
throw new Error("ArrayIndexOutOfBoundsException");
|
||||
}
|
||||
return this.array[index];
|
||||
}
|
||||
|
||||
safeGet(index: number): E {
|
||||
if (index >= this.length) {
|
||||
this.grow((index * 7) / 4 + 1);
|
||||
}
|
||||
return this.array[index];
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return this.size_;
|
||||
}
|
||||
|
||||
getCapacity(): number {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
isIndexWithinBounds(index: number): boolean {
|
||||
return index < this.getCapacity();
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return this.size_ == 0;
|
||||
}
|
||||
|
||||
add(e: E): void {
|
||||
if (this.size_ === this.length) {
|
||||
this.grow();
|
||||
}
|
||||
|
||||
this.array[this.size_++] = e;
|
||||
}
|
||||
|
||||
set(index: number, e: E): void {
|
||||
if (index >= this.length) {
|
||||
this.grow(index * 2);
|
||||
}
|
||||
this.size_ = index + 1;
|
||||
this.array[index] = e;
|
||||
}
|
||||
|
||||
grow(newCapacity: number = ~~((this.length * 3) / 2) + 1): void {
|
||||
this.length = ~~newCapacity;
|
||||
}
|
||||
|
||||
ensureCapacity(index: number): void {
|
||||
if (index >= this.length) {
|
||||
this.grow(index * 2);
|
||||
}
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
let i: number;
|
||||
let size: number;
|
||||
for (i = 0, size = this.size_; i < size; i++) {
|
||||
this.array[i] = null;
|
||||
}
|
||||
|
||||
this.size_ = 0;
|
||||
}
|
||||
|
||||
addAll(items: ImmutableBag<E>): void {
|
||||
let i: number;
|
||||
|
||||
for (i = 0; items.size() > i; i++) {
|
||||
this.add(items.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
module es {
|
||||
interface Map<K, V> {
|
||||
clear(): void;
|
||||
containsKey(key): boolean;
|
||||
containsValue(value): boolean;
|
||||
get(key): V;
|
||||
isEmpty(): boolean;
|
||||
put(key, value): void;
|
||||
remove(key): V;
|
||||
size(): number;
|
||||
values(): V[];
|
||||
}
|
||||
|
||||
function decode(key): string {
|
||||
switch (typeof key) {
|
||||
case "boolean":
|
||||
return "" + key;
|
||||
case "number":
|
||||
return "" + key;
|
||||
case "string":
|
||||
return "" + key;
|
||||
case "function":
|
||||
return getClassName(key);
|
||||
default:
|
||||
key.uuid = key.uuid ? key.uuid : UUID.randomUUID();
|
||||
return key.uuid;
|
||||
}
|
||||
}
|
||||
|
||||
export class HashMap<K, V> implements Map<K, V> {
|
||||
private map_;
|
||||
private keys_;
|
||||
|
||||
constructor() {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.map_ = {};
|
||||
this.keys_ = {};
|
||||
}
|
||||
|
||||
values(): V[] {
|
||||
const result = [];
|
||||
const map = this.map_;
|
||||
|
||||
for (const key in map) {
|
||||
result.push(map[key]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
contains(value): boolean {
|
||||
const map = this.map_;
|
||||
|
||||
for (const key in map) {
|
||||
if (value === map[key]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
containsKey(key): boolean {
|
||||
return decode(key) in this.map_;
|
||||
}
|
||||
|
||||
containsValue(value): boolean {
|
||||
const map = this.map_;
|
||||
|
||||
for (const key in map) {
|
||||
if (value === map[key]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get(key: K): V {
|
||||
return this.map_[decode(key)];
|
||||
}
|
||||
|
||||
isEmpty(): boolean {
|
||||
return Object.keys(this.map_).length === 0;
|
||||
}
|
||||
|
||||
keys(): K[] {
|
||||
const keys = this.map_;
|
||||
|
||||
const result = [];
|
||||
for (const key in keys) {
|
||||
result.push(keys[key]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* if key is a string, use as is, else use key.id_ or key.name
|
||||
*/
|
||||
put(key, value): void {
|
||||
const k = decode(key);
|
||||
this.map_[k] = value;
|
||||
this.keys_[k] = key;
|
||||
}
|
||||
|
||||
remove(key): V {
|
||||
const map = this.map_;
|
||||
const k = decode(key);
|
||||
const value = map[k];
|
||||
delete map[k];
|
||||
delete this.keys_[k];
|
||||
return value;
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return Object.keys(this.map_).length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
module es {
|
||||
export interface ImmutableBag<E> {
|
||||
get(index: number): E;
|
||||
|
||||
size(): number;
|
||||
|
||||
isEmpty(): boolean;
|
||||
|
||||
contains(e: E): boolean;
|
||||
}
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
module es {
|
||||
export class Node<T>{
|
||||
element: T;
|
||||
next: Node<T>;
|
||||
// next为可选参数,如果不传则为undefined
|
||||
constructor(element: T, next?: Node<T>) {
|
||||
this.element = element;
|
||||
this.next = next;
|
||||
}
|
||||
}
|
||||
|
||||
// 定义验证函数要传的参数和返回结果
|
||||
interface equalsFnType<T> {
|
||||
(a: T, b: T): boolean;
|
||||
}
|
||||
|
||||
export function defaultEquals<T>(a: T, b: T): boolean {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
export class LinkedList<T> {
|
||||
// 声明链表内需要的变量并定义其类型
|
||||
protected count: number;
|
||||
protected next: any;
|
||||
protected equalsFn: equalsFnType<T>;
|
||||
protected head: Node<T>;
|
||||
|
||||
constructor(equalsFn = defaultEquals) {
|
||||
// 初始化链表内部变量
|
||||
this.count = 0;
|
||||
this.next = undefined;
|
||||
this.equalsFn = equalsFn;
|
||||
this.head = null;
|
||||
}
|
||||
|
||||
// 链表尾部添加元素
|
||||
push(element: T) {
|
||||
// 声明结点变量,将元素当作参数传入生成结点
|
||||
const node = new Node(element);
|
||||
// 存储遍历到的链表元素
|
||||
let current;
|
||||
if (this.head == null) {
|
||||
// 链表为空,直接将链表头部赋值为结点变量
|
||||
this.head = node;
|
||||
} else {
|
||||
// 链表不为空,我们只能拿到链表中第一个元素的引用
|
||||
current = this.head;
|
||||
// 循环访问链表
|
||||
while (current.next != null) {
|
||||
// 赋值遍历到的元素
|
||||
current = current.next;
|
||||
}
|
||||
// 此时已经得到了链表的最后一个元素(null),将链表的下一个元素赋值为结点变量。
|
||||
current.next = node;
|
||||
}
|
||||
// 链表长度自增
|
||||
this.count++;
|
||||
}
|
||||
|
||||
// 移除链表指定位置的元素
|
||||
removeAt(index: number) {
|
||||
// 边界判断: 参数是否有效
|
||||
if (index >= 0 && index < this.count) {
|
||||
// 获取当前链表头部元素
|
||||
let current = this.head;
|
||||
// 移除第一项
|
||||
if (index === 0) {
|
||||
this.head = current.next;
|
||||
} else {
|
||||
// 获取目标参数上一个结点
|
||||
const previous = this.getElementAt(index - 1);
|
||||
// 当前结点指向目标结点
|
||||
current = previous.next;
|
||||
/**
|
||||
* 目标结点元素已找到
|
||||
* previous.next指向目标结点
|
||||
* current.next指向undefined
|
||||
* previous.next指向current.next即删除目标结点的元素
|
||||
*/
|
||||
previous.next = current.next;
|
||||
}
|
||||
// 链表长度自减
|
||||
this.count--;
|
||||
// 返回当前删除的目标结点
|
||||
return current.element;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 获取链表指定位置的结点
|
||||
getElementAt(index: number) {
|
||||
// 参数校验
|
||||
if (index >= 0 && index <= this.count) {
|
||||
// 获取链表头部元素
|
||||
let current = this.head;
|
||||
// 从链表头部遍历至目标结点位置
|
||||
for (let i = 0; i < index && current != null; i++) {
|
||||
// 当前结点指向下一个目标结点
|
||||
current = current.next;
|
||||
}
|
||||
// 返回目标结点数据
|
||||
return current;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 向链表中插入元素
|
||||
insert(element: T, index: number) {
|
||||
// 参数有效性判断
|
||||
if (index >= 0 && index <= this.count) {
|
||||
// 声明节点变量,将当前要插入的元素作为参数生成结点
|
||||
const node = new Node(element);
|
||||
// 第一个位置添加元素
|
||||
if (index === 0) {
|
||||
// 将节点变量(node)的下一个元素指向链表的头部元素
|
||||
node.next = this.head;
|
||||
// 链表头部元素赋值为节点变量
|
||||
this.head = node;
|
||||
} else {
|
||||
// 获取目标结点的上一个结点
|
||||
const previous = this.getElementAt(index - 1);
|
||||
// 将节点变量的下一个元素指向目标节点
|
||||
node.next = previous.next;
|
||||
/**
|
||||
* 此时node中当前结点为要插入的值
|
||||
* next为原位置处的结点
|
||||
* 因此将当前结点赋值为node,就完成了结点插入操作
|
||||
*/
|
||||
previous.next = node;
|
||||
}
|
||||
// 链表长度自增
|
||||
this.count++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据元素获取其在链表中的索引
|
||||
indexOf(element: T) {
|
||||
// 获取链表顶部元素
|
||||
let current = this.head;
|
||||
// 遍历链表内的元素
|
||||
for (let i = 0; i < this.count && current != null; i++) {
|
||||
// 判断当前链表中的结点与目标结点是否相等
|
||||
if (this.equalsFn(element, current.element)) {
|
||||
// 返回索引
|
||||
return i;
|
||||
}
|
||||
// 当前结点指向下一个结点
|
||||
current = current.next;
|
||||
}
|
||||
// 目标元素不存在
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 移除链表中的指定元素
|
||||
remove(element: T) {
|
||||
// 获取element的索引,移除索引位置的元素
|
||||
this.removeAt(this.indexOf(element));
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.head = undefined;
|
||||
this.count = 0;
|
||||
}
|
||||
|
||||
// 获取链表长度
|
||||
size() {
|
||||
return this.count;
|
||||
}
|
||||
|
||||
// 判断链表是否为空
|
||||
isEmpty() {
|
||||
return this.size() === 0;
|
||||
}
|
||||
|
||||
// 获取链表头部元素
|
||||
getHead() {
|
||||
return this.head;
|
||||
}
|
||||
|
||||
// 获取链表中的所有元素
|
||||
toString() {
|
||||
if (this.head == null) {
|
||||
return "";
|
||||
}
|
||||
let objString = `${this.head.element}`;
|
||||
// 获取链表顶点的下一个结点
|
||||
let current = this.head.next;
|
||||
// 遍历链表中的所有结点
|
||||
for (let i = 1; i < this.size() && current != null; i++) {
|
||||
// 将当前结点的元素拼接到最终要生成的字符串对象中
|
||||
objString = `${objString}, ${current.element}`;
|
||||
// 当前结点指向链表的下一个元素
|
||||
current = current.next;
|
||||
}
|
||||
return objString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 可以用于列表池的简单类
|
||||
*/
|
||||
export class ListPool {
|
||||
private static readonly _objectQueue: Map<any, any[]> = new Map();
|
||||
|
||||
/**
|
||||
* 预热缓存,使用最大的cacheCount对象填充缓存
|
||||
* @param cacheCount
|
||||
*/
|
||||
public static warmCache<T>(type: new (...args) => T, cacheCount: number) {
|
||||
this.checkCreate(type);
|
||||
cacheCount -= this._objectQueue.get(type).length;
|
||||
if (cacheCount > 0) {
|
||||
for (let i = 0; i < cacheCount; i++) {
|
||||
this._objectQueue.get(type).push([]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将缓存修剪为cacheCount项目
|
||||
* @param cacheCount
|
||||
*/
|
||||
public static trimCache<T>(type: new (...args) => T, cacheCount: number) {
|
||||
this.checkCreate(type);
|
||||
while (cacheCount > this._objectQueue.get(type).length)
|
||||
this._objectQueue.get(type).splice(0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
*/
|
||||
public static clearCache<T>(type: new (...args) => T) {
|
||||
this.checkCreate(type);
|
||||
this._objectQueue.get(type).length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果可以的话,从堆栈中弹出一个项
|
||||
*/
|
||||
public static obtain<T>(type: new (...args) => T): T[] {
|
||||
this.checkCreate(type);
|
||||
if (this._objectQueue.get(type).length > 0)
|
||||
return this._objectQueue.get(type).shift();
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 将项推回堆栈
|
||||
* @param obj
|
||||
*/
|
||||
public static free<T>(type: new (...args) => T, obj: T[]) {
|
||||
this.checkCreate(type);
|
||||
this._objectQueue.get(type).push(obj);
|
||||
obj.length = 0;
|
||||
}
|
||||
|
||||
private static checkCreate<T>(type: new (...args) => T) {
|
||||
if (!this._objectQueue.has(type))
|
||||
this._objectQueue.set(type, []);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 用于管理一对对象的简单DTO
|
||||
*/
|
||||
export class Pair<T> {
|
||||
public first: T;
|
||||
public second: T;
|
||||
|
||||
constructor(first: T, second: T) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.first = this.second = null;
|
||||
}
|
||||
|
||||
public equals(other: Pair<T>): boolean {
|
||||
// 这两种方法在功能上应该是等价的
|
||||
return this.first === other.first && this.second === other.second;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
module es {
|
||||
export class PairSet<T> {
|
||||
public get all(): Array<Pair<T>> {
|
||||
return this._all;
|
||||
}
|
||||
|
||||
public has(pair: Pair<T>) {
|
||||
const index = this._all.findIndex(p => p.equals(pair));
|
||||
return index > -1;
|
||||
}
|
||||
|
||||
public add(pair: Pair<T>) {
|
||||
if (!this.has(pair)) {
|
||||
this._all.push(pair);
|
||||
}
|
||||
}
|
||||
|
||||
public remove(pair: Pair<T>) {
|
||||
const index = this._all.findIndex(p => p.equals(pair));
|
||||
if (index > -1) {
|
||||
const temp = this._all[index];
|
||||
this._all[index] = this._all[this._all.length - 1];
|
||||
this._all[this._all.length - 1] = temp;
|
||||
this._all = this._all.slice(0, this._all.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this._all = [];
|
||||
}
|
||||
|
||||
public union(other: PairSet<T>) {
|
||||
const otherAll = other.all;
|
||||
|
||||
if (otherAll.length > 0)
|
||||
for (let i = 0; i < otherAll.length; i ++) {
|
||||
const elem = otherAll[i];
|
||||
this.add(elem);
|
||||
}
|
||||
}
|
||||
|
||||
public except(other: PairSet<T>) {
|
||||
const otherAll = other.all;
|
||||
|
||||
if (otherAll.length > 0)
|
||||
for (let i = 0; i < otherAll.length; i ++) {
|
||||
const elem = otherAll[i];
|
||||
this.remove(elem);
|
||||
}
|
||||
}
|
||||
|
||||
private _all: Array<Pair<T>> = new Array<Pair<T>>();
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
module es {
|
||||
export class Pool {
|
||||
private static _objectQueue = new Map<new () => any, any[]>();
|
||||
|
||||
/**
|
||||
* 预热缓存,使用最大的cacheCount对象填充缓存
|
||||
* @param type 要预热的类型
|
||||
* @param cacheCount 预热缓存数量
|
||||
*/
|
||||
public static warmCache<T>(type: new (...args: any[]) => T, cacheCount: number) {
|
||||
this.checkCreate(type);
|
||||
const queue = this._objectQueue.get(type);
|
||||
cacheCount -= queue.length;
|
||||
|
||||
// 如果需要预热更多的对象,则创建并添加到缓存
|
||||
if (cacheCount > 0) {
|
||||
for (let i = 0; i < cacheCount; i++) {
|
||||
queue.push(new type());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将缓存修剪为cacheCount项目
|
||||
* @param type 要修剪的类型
|
||||
* @param cacheCount 修剪后的缓存数量
|
||||
*/
|
||||
public static trimCache<T>(type: new (...args) => T, cacheCount: number) {
|
||||
this.checkCreate(type);
|
||||
const objectQueue = this._objectQueue.get(type);
|
||||
|
||||
// 如果需要修剪缓存,则弹出多余的对象
|
||||
while (cacheCount < objectQueue.length) {
|
||||
objectQueue.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
* @param type 要清除缓存的类型
|
||||
*/
|
||||
public static clearCache<T>(type: new (...args) => T) {
|
||||
this.checkCreate(type);
|
||||
const objectQueue = this._objectQueue.get(type);
|
||||
|
||||
// 清空缓存数组
|
||||
objectQueue.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果可以的话,从缓存中获取一个对象
|
||||
* @param type 要获取的类型
|
||||
*/
|
||||
public static obtain<T>(type: new (...args) => T): T {
|
||||
this.checkCreate(type);
|
||||
const objectQueue = this._objectQueue.get(type);
|
||||
|
||||
// 如果缓存中有对象,弹出一个并返回
|
||||
if (objectQueue.length > 0) {
|
||||
return objectQueue.pop();
|
||||
}
|
||||
|
||||
// 如果没有缓存对象,则创建一个新的对象并返回
|
||||
return new type() as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象推回缓存
|
||||
* @param type 对象的类型
|
||||
* @param obj 要推回的对象
|
||||
*/
|
||||
public static free<T>(type: new (...args) => T, obj: T) {
|
||||
this.checkCreate(type);
|
||||
const objectQueue = this._objectQueue.get(type);
|
||||
|
||||
// 将对象推回缓存
|
||||
objectQueue.push(obj);
|
||||
|
||||
// 如果对象实现了IPoolable接口,则调用reset方法重置对象
|
||||
if (isIPoolable(obj)) {
|
||||
obj.reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查缓存中是否已存在给定类型的对象池,如果不存在则创建一个
|
||||
* @param type 要检查的类型
|
||||
*/
|
||||
private static checkCreate<T>(type: new (...args: any[]) => T) {
|
||||
if (!this._objectQueue.has(type)) {
|
||||
this._objectQueue.set(type, []);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface IPoolable {
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
export const isIPoolable = (props: any): props is IPoolable => {
|
||||
return typeof props.reset === 'function';
|
||||
};
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* startCoroutine返回的接口,它提供了中途停止coroutine的能力。
|
||||
*/
|
||||
export interface ICoroutine {
|
||||
/**
|
||||
* 停止Coroutine
|
||||
*/
|
||||
stop();
|
||||
/**
|
||||
* 设置Coroutine是否应该使用deltaTime或unscaledDeltaTime进行计时
|
||||
* @param useUnscaledDeltaTime
|
||||
*/
|
||||
setUseUnscaledDeltaTime(useUnscaledDeltaTime: boolean): ICoroutine;
|
||||
}
|
||||
|
||||
export class Coroutine {
|
||||
/**
|
||||
* 导致Coroutine在指定的时间内暂停。在Coroutine.waitForSeconds的基础上,在Coroutine中使用Yield
|
||||
* @param seconds
|
||||
*/
|
||||
public static waitForSeconds(seconds: number) {
|
||||
return WaitForSeconds.waiter.wait(seconds);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 帮助类,用于当一个coroutine想要暂停一段时间时。返回Coroutine.waitForSeconds返回其中一个
|
||||
*/
|
||||
export class WaitForSeconds {
|
||||
public static waiter: WaitForSeconds = new WaitForSeconds();
|
||||
public waitTime: number = 0;
|
||||
|
||||
public wait(seconds: number): WaitForSeconds {
|
||||
WaitForSeconds.waiter.waitTime = seconds;
|
||||
return WaitForSeconds.waiter;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* CoroutineManager用于隐藏Coroutine所需数据的内部类
|
||||
*/
|
||||
export class CoroutineImpl implements ICoroutine, IPoolable {
|
||||
public enumerator;
|
||||
|
||||
/**
|
||||
* 每当产生一个延迟,它就会被添加到跟踪延迟的waitTimer中
|
||||
*/
|
||||
public waitTimer: number = 0;
|
||||
|
||||
public isDone: boolean;
|
||||
public waitForCoroutine: CoroutineImpl;
|
||||
public useUnscaledDeltaTime: boolean = false;
|
||||
|
||||
public stop() {
|
||||
this.isDone = true;
|
||||
}
|
||||
|
||||
public setUseUnscaledDeltaTime(useUnscaledDeltaTime: boolean) {
|
||||
this.useUnscaledDeltaTime = useUnscaledDeltaTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
public prepareForUse() {
|
||||
this.isDone = false;
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this.isDone = true;
|
||||
this.waitTimer = 0;
|
||||
this.waitForCoroutine = null;
|
||||
this.enumerator = null;
|
||||
this.useUnscaledDeltaTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
export class CoroutineManager extends GlobalManager {
|
||||
/**
|
||||
* 标志来跟踪我们何时处于更新循环中。
|
||||
* 如果在更新循环中启动了一个新的coroutine,我们必须将它贴在shouldRunNextFrame列表中,以避免在迭代时修改一个数组
|
||||
*/
|
||||
public _isInUpdate: boolean;
|
||||
|
||||
public _unblockedCoroutines: CoroutineImpl[] = [];
|
||||
public _shouldRunNextFrame: CoroutineImpl[] = [];
|
||||
|
||||
/**
|
||||
* 立即停止并清除所有协程
|
||||
*/
|
||||
public clearAllCoroutines() {
|
||||
for (let i = 0; i < this._unblockedCoroutines.length; i++) {
|
||||
Pool.free(CoroutineImpl, this._unblockedCoroutines[i]);
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._shouldRunNextFrame.length; i++) {
|
||||
Pool.free(CoroutineImpl, this._shouldRunNextFrame[i]);
|
||||
}
|
||||
|
||||
this._unblockedCoroutines.length = 0;
|
||||
this._shouldRunNextFrame.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将IEnumerator添加到CoroutineManager中
|
||||
* Coroutine在每一帧调用Update之前被执行
|
||||
* @param enumerator
|
||||
*/
|
||||
public startCoroutine<T>(enumerator: Iterator<T> | (() => Iterator<T>)): CoroutineImpl | null {
|
||||
const coroutine = this.getOrCreateCoroutine();
|
||||
coroutine.prepareForUse();
|
||||
coroutine.enumerator = typeof enumerator === 'function' ? enumerator() : enumerator;
|
||||
|
||||
if (this.tickCoroutine(coroutine)) {
|
||||
this.addCoroutine(coroutine);
|
||||
return coroutine;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private getOrCreateCoroutine(): CoroutineImpl {
|
||||
const coroutine = Pool.obtain<CoroutineImpl>(CoroutineImpl);
|
||||
coroutine.prepareForUse();
|
||||
return coroutine;
|
||||
}
|
||||
|
||||
private addCoroutine(coroutine: CoroutineImpl) {
|
||||
if (this._isInUpdate)
|
||||
this._shouldRunNextFrame.push(coroutine);
|
||||
else
|
||||
this._unblockedCoroutines.push(coroutine);
|
||||
}
|
||||
|
||||
public update() {
|
||||
this._isInUpdate = true;
|
||||
|
||||
const unblockedCoroutines = this._unblockedCoroutines;
|
||||
const shouldRunNextFrame = this._shouldRunNextFrame;
|
||||
|
||||
for (let i = unblockedCoroutines.length - 1; i >= 0; i--) {
|
||||
const coroutine = unblockedCoroutines[i];
|
||||
|
||||
if (coroutine.isDone) {
|
||||
Pool.free(CoroutineImpl, coroutine);
|
||||
unblockedCoroutines.splice(i, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
const waitForCoroutine = coroutine.waitForCoroutine;
|
||||
if (waitForCoroutine != null) {
|
||||
if (waitForCoroutine.isDone) {
|
||||
coroutine.waitForCoroutine = null;
|
||||
} else {
|
||||
shouldRunNextFrame.push(coroutine);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const waitTimer = coroutine.waitTimer;
|
||||
if (waitTimer > 0) {
|
||||
// 递减,然后再运行下一帧,确保用适当的deltaTime递减
|
||||
coroutine.waitTimer = waitTimer - (coroutine.useUnscaledDeltaTime ? Time.unscaledDeltaTime : Time.deltaTime);
|
||||
shouldRunNextFrame.push(coroutine);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.tickCoroutine(coroutine)) {
|
||||
shouldRunNextFrame.push(coroutine);
|
||||
}
|
||||
}
|
||||
|
||||
unblockedCoroutines.push(...shouldRunNextFrame);
|
||||
shouldRunNextFrame.length = 0;
|
||||
|
||||
this._isInUpdate = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 勾选一个coroutine,如果该coroutine应该在下一帧继续运行,则返回true。本方法会将完成的coroutine放回Pool
|
||||
* @param coroutine
|
||||
*/
|
||||
public tickCoroutine(coroutine: CoroutineImpl) {
|
||||
const { enumerator } = coroutine;
|
||||
const { value, done } = enumerator.next();
|
||||
|
||||
if (done || coroutine.isDone) {
|
||||
// 当协程执行完或标记为结束时,回收协程实例并返回 false。
|
||||
Pool.free(CoroutineImpl, coroutine);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
// 如果下一帧没有指定任务,返回 true 让协程继续等待下一帧执行。
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value instanceof WaitForSeconds) {
|
||||
// 如果下一帧需要等待指定时间,则记录等待时间并返回 true。
|
||||
coroutine.waitTimer = value.waitTime;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof value === 'number') {
|
||||
// 如果下一帧需要等待指定时间,则记录等待时间并返回 true。
|
||||
coroutine.waitTimer = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
// 如果下一帧返回 'break',标记协程为结束并返回 false。
|
||||
if (value === 'break') {
|
||||
Pool.free(CoroutineImpl, coroutine);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 否则返回 true 让协程继续等待下一帧执行。
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof value === 'function') {
|
||||
// 如果下一帧需要等待另一个协程完成,启动并记录另一个协程实例,并返回 true。
|
||||
coroutine.waitForCoroutine = this.startCoroutine(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value instanceof CoroutineImpl) {
|
||||
// 如果下一帧需要等待另一个协程完成,记录另一个协程实例,并返回 true。
|
||||
coroutine.waitForCoroutine = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 否则返回 true 让协程继续等待下一帧执行。
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
module es {
|
||||
export class MaxRectsBinPack {
|
||||
public binWidth: number = 0;
|
||||
public binHeight: number = 0;
|
||||
public allowRotations: boolean;
|
||||
|
||||
public usedRectangles: Rectangle[] = [];
|
||||
public freeRectangles: Rectangle[] = [];
|
||||
|
||||
constructor(width: number, height: number, rotations: boolean = true) {
|
||||
this.init(width, height, rotations);
|
||||
}
|
||||
|
||||
public init(width: number, height: number, rotations: boolean = true) {
|
||||
this.binWidth = width;
|
||||
this.binHeight = height;
|
||||
this.allowRotations = rotations;
|
||||
|
||||
let n = new Rectangle();
|
||||
n.x = 0;
|
||||
n.y = 0;
|
||||
n.width = width;
|
||||
n.height = height;
|
||||
|
||||
this.usedRectangles.length = 0;
|
||||
|
||||
this.freeRectangles.length = 0;
|
||||
this.freeRectangles.push(n);
|
||||
}
|
||||
|
||||
public insert(width: number, height: number): Rectangle {
|
||||
let newNode = new Rectangle();
|
||||
let score1 = new Ref(0);
|
||||
let score2 = new Ref(0);
|
||||
newNode = this.findPositionForNewNodeBestAreaFit(width, height, score1, score2);
|
||||
|
||||
if (newNode.height == 0)
|
||||
return newNode;
|
||||
|
||||
let numRectanglesToProcess = this.freeRectangles.length;
|
||||
for (let i = 0; i < numRectanglesToProcess; ++i) {
|
||||
if (this.splitFreeNode(this.freeRectangles[i], newNode)) {
|
||||
new es.List(this.freeRectangles).removeAt(i);
|
||||
--i;
|
||||
--numRectanglesToProcess;
|
||||
}
|
||||
}
|
||||
|
||||
this.pruneFreeList();
|
||||
|
||||
this.usedRectangles.push(newNode);
|
||||
return newNode;
|
||||
}
|
||||
|
||||
public findPositionForNewNodeBestAreaFit(width: number, height: number, bestAreaFit: Ref<number>, bestShortSideFit: Ref<number>) {
|
||||
let bestNode = new Rectangle();
|
||||
|
||||
bestAreaFit.value = Number.MAX_VALUE;
|
||||
|
||||
for (let i = 0; i < this.freeRectangles.length; ++i) {
|
||||
let areaFit = this.freeRectangles[i].width * this.freeRectangles[i].height - width * height;
|
||||
|
||||
// 试着将长方形放在直立(非翻转)的方向
|
||||
if (this.freeRectangles[i].width >= width && this.freeRectangles[i].height >= height) {
|
||||
let leftoverHoriz = Math.abs(this.freeRectangles[i].width - width);
|
||||
let leftoverVert = Math.abs(this.freeRectangles[i].height - height);
|
||||
let shortSideFit = Math.min(leftoverHoriz, leftoverVert);
|
||||
|
||||
if (areaFit < bestAreaFit.value || (areaFit == bestAreaFit.value && shortSideFit < bestShortSideFit.value)) {
|
||||
bestNode.x = this.freeRectangles[i].x;
|
||||
bestNode.y = this.freeRectangles[i].y;
|
||||
bestNode.width = width;
|
||||
bestNode.height = height;
|
||||
bestShortSideFit.value = shortSideFit;
|
||||
bestAreaFit.value = areaFit;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.allowRotations && this.freeRectangles[i].width >= height && this.freeRectangles[i].height >= width) {
|
||||
let leftoverHoriz = Math.abs(this.freeRectangles[i].width - height);
|
||||
let leftoverVert = Math.abs(this.freeRectangles[i].height - width);
|
||||
let shortSideFit = Math.min(leftoverHoriz, leftoverVert);
|
||||
|
||||
if (areaFit < bestAreaFit.value || (areaFit == bestAreaFit.value && shortSideFit < bestShortSideFit.value)) {
|
||||
bestNode.x = this.freeRectangles[i].x;
|
||||
bestNode.y = this.freeRectangles[i].y;
|
||||
bestNode.width = height;
|
||||
bestNode.height = width;
|
||||
bestShortSideFit.value = shortSideFit;
|
||||
bestAreaFit.value = areaFit;
|
||||
}
|
||||
}
|
||||
|
||||
return bestNode;
|
||||
}
|
||||
}
|
||||
|
||||
public splitFreeNode(freeNode: Rectangle, usedNode: Rectangle) {
|
||||
// 用SAT测试长方形是否均匀相交
|
||||
if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x ||
|
||||
usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y)
|
||||
return false;
|
||||
|
||||
if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x) {
|
||||
// 在使用过的节点的上边新建一个节点
|
||||
if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height) {
|
||||
let newNode = freeNode;
|
||||
newNode.height = usedNode.y - newNode.y;
|
||||
this.freeRectangles.push(newNode);
|
||||
}
|
||||
|
||||
// 在使用过的节点的底边新建节点
|
||||
if (usedNode.y + usedNode.height < freeNode.y + freeNode.height) {
|
||||
let newNode = freeNode;
|
||||
newNode.y = usedNode.y + usedNode.height;
|
||||
newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
|
||||
this.freeRectangles.push(newNode);
|
||||
}
|
||||
}
|
||||
|
||||
if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y) {
|
||||
// 在使用过的节点的左侧新建节点
|
||||
if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width) {
|
||||
let newNode = freeNode;
|
||||
newNode.width = usedNode.x - newNode.x;
|
||||
this.freeRectangles.push(newNode);
|
||||
}
|
||||
|
||||
// 在使用过的节点右侧新建节点
|
||||
if (usedNode.x + usedNode.width < freeNode.x + freeNode.width) {
|
||||
let newNode = freeNode;
|
||||
newNode.x = usedNode.x + usedNode.width;
|
||||
newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
|
||||
this.freeRectangles.push(newNode);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public pruneFreeList() {
|
||||
for (let i = 0; i < this.freeRectangles.length; ++i)
|
||||
for (let j = i + 1; j < this.freeRectangles.length; ++j) {
|
||||
if (this.isContainedIn(this.freeRectangles[i], this.freeRectangles[j])) {
|
||||
new es.List(this.freeRectangles).removeAt(i);
|
||||
--i;
|
||||
break;
|
||||
}
|
||||
if (this.isContainedIn(this.freeRectangles[j], this.freeRectangles[i])) {
|
||||
new es.List(this.freeRectangles).removeAt(j);
|
||||
--j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public isContainedIn(a: Rectangle, b: Rectangle) {
|
||||
return a.x >= b.x && a.y >= b.y
|
||||
&& a.x + a.width <= b.x + b.width
|
||||
&& a.y + a.height <= b.y + b.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +1,81 @@
|
||||
module es {
|
||||
/**
|
||||
* 用于包装事件的一个小类
|
||||
*/
|
||||
export class FuncPack {
|
||||
/** 函数 */
|
||||
public func: Function;
|
||||
/** 上下文 */
|
||||
public context: any;
|
||||
/**
|
||||
* 用于包装事件的一个小类
|
||||
*/
|
||||
export class FuncPack {
|
||||
/** 函数 */
|
||||
public func: Function;
|
||||
/** 上下文 */
|
||||
public context: any;
|
||||
|
||||
constructor(func: Function, context: any) {
|
||||
this.func = func;
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于事件管理
|
||||
*/
|
||||
export class Emitter<T> {
|
||||
private _messageTable: Map<T, FuncPack[]>;
|
||||
|
||||
constructor() {
|
||||
this._messageTable = new Map<T, FuncPack[]>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始监听项
|
||||
* @param eventType 监听类型
|
||||
* @param handler 监听函数
|
||||
* @param context 监听上下文
|
||||
*/
|
||||
public addObserver(eventType: T, handler: Function, context: any) {
|
||||
let list = this._messageTable.get(eventType);
|
||||
if (!list) {
|
||||
list = [];
|
||||
this._messageTable.set(eventType, list);
|
||||
}
|
||||
|
||||
if (!this.hasObserver(eventType, handler)) {
|
||||
list.push(new FuncPack(handler, context));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听项
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件函数
|
||||
*/
|
||||
public removeObserver(eventType: T, handler: Function) {
|
||||
let messageData = this._messageTable.get(eventType);
|
||||
if (messageData) {
|
||||
let index = messageData.findIndex(data => data.func == handler);
|
||||
if (index != -1)
|
||||
messageData.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发该事件
|
||||
* @param eventType 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
public emit(eventType: T, ...data: any[]) {
|
||||
let list = this._messageTable.get(eventType);
|
||||
if (list) {
|
||||
for (let observer of list) {
|
||||
observer.func.call(observer.context, ...data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在该类型的观察者
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件函数
|
||||
*/
|
||||
public hasObserver(eventType: T, handler: Function): boolean {
|
||||
let list = this._messageTable.get(eventType);
|
||||
return list ? list.some(observer => observer.func === handler) : false;
|
||||
}
|
||||
constructor(func: Function, context: any) {
|
||||
this.func = func;
|
||||
this.context = context;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于事件管理
|
||||
*/
|
||||
export class Emitter<T> {
|
||||
private _messageTable: Map<T, FuncPack[]>;
|
||||
|
||||
constructor() {
|
||||
this._messageTable = new Map<T, FuncPack[]>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始监听项
|
||||
* @param eventType 监听类型
|
||||
* @param handler 监听函数
|
||||
* @param context 监听上下文
|
||||
*/
|
||||
public addObserver(eventType: T, handler: Function, context: any) {
|
||||
let list = this._messageTable.get(eventType);
|
||||
if (!list) {
|
||||
list = [];
|
||||
this._messageTable.set(eventType, list);
|
||||
}
|
||||
|
||||
if (!this.hasObserver(eventType, handler)) {
|
||||
list.push(new FuncPack(handler, context));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除监听项
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件函数
|
||||
*/
|
||||
public removeObserver(eventType: T, handler: Function) {
|
||||
let messageData = this._messageTable.get(eventType);
|
||||
if (messageData) {
|
||||
let index = messageData.findIndex(data => data.func == handler);
|
||||
if (index != -1)
|
||||
messageData.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发该事件
|
||||
* @param eventType 事件类型
|
||||
* @param data 事件数据
|
||||
*/
|
||||
public emit(eventType: T, ...data: any[]) {
|
||||
let list = this._messageTable.get(eventType);
|
||||
if (list) {
|
||||
for (let observer of list) {
|
||||
observer.func.call(observer.context, ...data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否存在该类型的观察者
|
||||
* @param eventType 事件类型
|
||||
* @param handler 事件函数
|
||||
*/
|
||||
public hasObserver(eventType: T, handler: Function): boolean {
|
||||
let list = this._messageTable.get(eventType);
|
||||
return list ? list.some(observer => observer.func === handler) : false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
module es {
|
||||
export enum Edge {
|
||||
top,
|
||||
bottom,
|
||||
left,
|
||||
right
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
module es {
|
||||
export class EqualityComparer<T> implements IEqualityComparer<T> {
|
||||
public static default<T>() {
|
||||
return new EqualityComparer<T>();
|
||||
}
|
||||
|
||||
protected constructor() { }
|
||||
|
||||
public equals(x: T, y: T): boolean {
|
||||
if (typeof x["equals"] == 'function') {
|
||||
return x["equals"](y);
|
||||
} else {
|
||||
return x === y;
|
||||
}
|
||||
}
|
||||
|
||||
public getHashCode(o: T): number {
|
||||
if (typeof o == 'number') {
|
||||
return this._getHashCodeForNumber(o);
|
||||
}
|
||||
|
||||
if (typeof o == 'string') {
|
||||
return this._getHashCodeForString(o);
|
||||
}
|
||||
|
||||
let hashCode = 385229220;
|
||||
this.forOwn(o, (value) => {
|
||||
if (typeof value == 'number') {
|
||||
hashCode += this._getHashCodeForNumber(value);
|
||||
} else if (typeof value == 'string') {
|
||||
hashCode += this._getHashCodeForString(value);
|
||||
} else if (typeof value == 'object') {
|
||||
this.forOwn(value, () => {
|
||||
hashCode += this.getHashCode(value);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
private _getHashCodeForNumber(n: number): number {
|
||||
return n;
|
||||
}
|
||||
|
||||
private _getHashCodeForString(s: string): number {
|
||||
let hashCode = 385229220;
|
||||
|
||||
for (let i = 0; i < s.length; i++) {
|
||||
hashCode = (hashCode * -1521134295) ^ s.charCodeAt(i);
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
private forOwn(object, iteratee){
|
||||
object = Object(object);
|
||||
Object.keys(object).forEach((key) => iteratee(object[key], key, object));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
module es {
|
||||
export class ArrayUtils {
|
||||
/**
|
||||
* 执行冒泡排序
|
||||
* @param ary
|
||||
*/
|
||||
public static bubbleSort(ary: number[]): void {
|
||||
let isExchange: Boolean = false;
|
||||
for (let i: number = 0; i < ary.length; i++) {
|
||||
isExchange = false;
|
||||
for (let j: number = ary.length - 1; j > i; j--) {
|
||||
if (ary[j] < ary[j - 1]) {
|
||||
let temp: number = ary[j];
|
||||
ary[j] = ary[j - 1];
|
||||
ary[j - 1] = temp;
|
||||
isExchange = true;
|
||||
}
|
||||
}
|
||||
if (!isExchange)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行插入排序
|
||||
* @param ary
|
||||
*/
|
||||
public static insertionSort(ary: number[]): void {
|
||||
let len: number = ary.length;
|
||||
for (let i: number = 1; i < len; i++) {
|
||||
let val: number = ary[i];
|
||||
for (var j: number = i; j > 0 && ary[j - 1] > val; j--) {
|
||||
ary[j] = ary[j - 1];
|
||||
}
|
||||
ary[j] = val;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行二分搜索
|
||||
* @param ary 搜索的数组(必须排序过)
|
||||
* @param value 需要搜索的值
|
||||
* @returns 返回匹配结果的数组索引
|
||||
*/
|
||||
public static binarySearch(ary: number[], value: number): number {
|
||||
let startIndex: number = 0;
|
||||
let endIndex: number = ary.length;
|
||||
let sub: number = (startIndex + endIndex) >> 1;
|
||||
while (startIndex < endIndex) {
|
||||
if (value <= ary[sub]) endIndex = sub;
|
||||
else if (value >= ary[sub]) startIndex = sub + 1;
|
||||
sub = (startIndex + endIndex) >> 1;
|
||||
}
|
||||
if (ary[startIndex] == value) return startIndex;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回匹配项的索引
|
||||
* @param ary
|
||||
* @param num
|
||||
*/
|
||||
public static findElementIndex(ary: any[], num: any): any {
|
||||
let len: number = ary.length;
|
||||
for (let i: number = 0; i < len; ++i) {
|
||||
if (ary[i] == num)
|
||||
return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数组中最大值的索引
|
||||
* @param ary
|
||||
*/
|
||||
public static getMaxElementIndex(ary: number[]): number {
|
||||
let matchIndex: number = 0;
|
||||
let len: number = ary.length;
|
||||
for (let j: number = 1; j < len; j++) {
|
||||
if (ary[j] > ary[matchIndex])
|
||||
matchIndex = j;
|
||||
}
|
||||
return matchIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数组中最小值的索引
|
||||
* @param ary
|
||||
*/
|
||||
public static getMinElementIndex(ary: number[]): number {
|
||||
let matchIndex: number = 0;
|
||||
let len: number = ary.length;
|
||||
for (let j: number = 1; j < len; j++) {
|
||||
if (ary[j] < ary[matchIndex])
|
||||
matchIndex = j;
|
||||
}
|
||||
return matchIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个"唯一性"数组
|
||||
* @param ary 需要唯一性的数组
|
||||
* @returns 唯一性的数组
|
||||
*
|
||||
* @tutorial
|
||||
* 比如: [1, 2, 2, 3, 4]
|
||||
* 返回: [1, 2, 3, 4]
|
||||
*/
|
||||
public static getUniqueAry(ary: number[]): number[] {
|
||||
let uAry: number[] = [];
|
||||
let newAry: number[] = [];
|
||||
let count = ary.length;
|
||||
for (let i: number = 0; i < count; ++i) {
|
||||
let value: number = ary[i];
|
||||
if (uAry.indexOf(value) == -1) uAry.push(value);
|
||||
}
|
||||
|
||||
count = uAry.length;
|
||||
for (let i: number = count - 1; i >= 0; --i) {
|
||||
newAry.unshift(uAry[i]);
|
||||
}
|
||||
return newAry;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回2个数组中不同的部分
|
||||
* 比如数组A = [1, 2, 3, 4, 6]
|
||||
* 数组B = [0, 2, 1, 3, 4]
|
||||
* 返回[6, 0]
|
||||
* @param aryA
|
||||
* @param aryB
|
||||
* @return
|
||||
*/
|
||||
public static getDifferAry(aryA: number[], aryB: number[]): number[] {
|
||||
aryA = this.getUniqueAry(aryA);
|
||||
aryB = this.getUniqueAry(aryB);
|
||||
let ary: number[] = aryA.concat(aryB);
|
||||
let uObj: Object = {};
|
||||
let newAry: number[] = [];
|
||||
let count: number = ary.length;
|
||||
for (let j: number = 0; j < count; ++j) {
|
||||
if (!uObj[ary[j]]) {
|
||||
uObj[ary[j]] = {};
|
||||
uObj[ary[j]].count = 0;
|
||||
uObj[ary[j]].key = ary[j];
|
||||
uObj[ary[j]].count++;
|
||||
} else {
|
||||
if (uObj[ary[j]] instanceof Object) {
|
||||
uObj[ary[j]].count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i in uObj) {
|
||||
if (uObj[i].count != 2) {
|
||||
newAry.unshift(uObj[i].key);
|
||||
}
|
||||
}
|
||||
return newAry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 交换数组元素
|
||||
* @param array 目标数组
|
||||
* @param index1 交换后的索引
|
||||
* @param index2 交换前的索引
|
||||
*/
|
||||
public static swap(array: any[], index1: number, index2: number): void {
|
||||
let temp: any = array[index1];
|
||||
array[index1] = array[index2];
|
||||
array[index2] = temp;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 清除列表
|
||||
* @param ary
|
||||
*/
|
||||
public static clearList(ary: any[]): void {
|
||||
if (!ary) return;
|
||||
let length: number = ary.length;
|
||||
for (let i: number = length - 1; i >= 0; i -= 1) {
|
||||
ary.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 克隆一个数组
|
||||
* @param ary 需要克隆的数组
|
||||
* @return 克隆的数组
|
||||
*/
|
||||
public static cloneList(ary: any[]): any[] {
|
||||
if (!ary) return null;
|
||||
return ary.slice(0, ary.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断2个数组是否相同
|
||||
* @param ary1 数组1
|
||||
* @param ary2 数组2
|
||||
*/
|
||||
public static equals(ary1: number[], ary2: number[]): Boolean {
|
||||
if (ary1 == ary2) return true;
|
||||
let length: number = ary1.length;
|
||||
if (length != ary2.length) return false;
|
||||
while (length--) {
|
||||
if (ary1[length] != ary2[length])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据索引插入元素,索引和索引后的元素都向后移动一位
|
||||
* @param ary
|
||||
* @param index 插入索引
|
||||
* @param value 插入的元素
|
||||
* @returns 插入的元素 未插入则返回空
|
||||
*/
|
||||
public static insert(ary: any[], index: number, value: any): any {
|
||||
if (!ary) return null;
|
||||
let length: number = ary.length;
|
||||
if (index > length) index = length;
|
||||
if (index < 0) index = 0;
|
||||
if (index == length) ary.push(value); //插入最后
|
||||
else if (index == 0) ary.unshift(value); //插入头
|
||||
else {
|
||||
for (let i: number = length - 1; i >= index; i -= 1) {
|
||||
ary[i + 1] = ary[i];
|
||||
}
|
||||
ary[index] = value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打乱数组 Fisher–Yates shuffle
|
||||
* @param list
|
||||
*/
|
||||
public static shuffle<T>(list: T[]) {
|
||||
let n = list.length;
|
||||
while (n > 1) {
|
||||
n--;
|
||||
let k = RandomUtils.randint(0, n + 1);
|
||||
let value: T = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果项目已经在列表中,返回false,如果成功添加,返回true
|
||||
* @param list
|
||||
* @param item
|
||||
*/
|
||||
public static addIfNotPresent<T>(list: T[], item: T) {
|
||||
if (new es.List(list).contains(item))
|
||||
return false;
|
||||
|
||||
list.push(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回列表中的最后一项。列表中至少应该有一个项目
|
||||
* @param list
|
||||
*/
|
||||
public static lastItem<T>(list: T[]) {
|
||||
return list[list.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 从列表中随机获取一个项目。不清空检查列表!
|
||||
* @param list
|
||||
*/
|
||||
public static randomItem<T>(list: T[]) {
|
||||
return list[RandomUtils.randint(0, list.length - 1)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 从列表中随机获取物品。不清空检查列表,也不验证列表数是否大于项目数。返回的List可以通过ListPool.free放回池中
|
||||
* @param list
|
||||
* @param itemCount 从列表中返回的随机项目的数量
|
||||
*/
|
||||
public static randomItems<T>(type: any, list: T[], itemCount: number){
|
||||
let set = new Set<T>();
|
||||
while (set.size != itemCount) {
|
||||
let item = this.randomItem(list);
|
||||
if (!set.has(item))
|
||||
set.add(item);
|
||||
}
|
||||
|
||||
let items = es.ListPool.obtain<T>(type);
|
||||
set.forEach(value => items.push(value));
|
||||
return items;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
module es{
|
||||
export class Base64Utils {
|
||||
private static _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
|
||||
/**
|
||||
* 判断是否原生支持Base64位解析
|
||||
*/
|
||||
static get nativeBase64() {
|
||||
return (typeof (window.atob) === "function");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解码
|
||||
* @param input
|
||||
*/
|
||||
static decode(input:string): string {
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
|
||||
if (this.nativeBase64) {
|
||||
return window.atob(input);
|
||||
} else {
|
||||
var output: any = [], chr1: number, chr2: number, chr3: number, enc1: number, enc2: number, enc3: number, enc4: number, i: number = 0;
|
||||
|
||||
while (i < input.length) {
|
||||
enc1 = this._keyStr.indexOf(input.charAt(i++));
|
||||
enc2 = this._keyStr.indexOf(input.charAt(i++));
|
||||
enc3 = this._keyStr.indexOf(input.charAt(i++));
|
||||
enc4 = this._keyStr.indexOf(input.charAt(i++));
|
||||
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
|
||||
output.push(String.fromCharCode(chr1));
|
||||
|
||||
if (enc3 !== 64) {
|
||||
output.push(String.fromCharCode(chr2));
|
||||
}
|
||||
if (enc4 !== 64) {
|
||||
output.push(String.fromCharCode(chr3));
|
||||
}
|
||||
}
|
||||
|
||||
output = output.join("");
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 编码
|
||||
* @param input
|
||||
*/
|
||||
static encode(input:string): string {
|
||||
input = input.replace(/\r\n/g, "\n");
|
||||
if (this.nativeBase64) {
|
||||
window.btoa(input);
|
||||
} else {
|
||||
var output: any = [], chr1: number, chr2: number, chr3: number, enc1: number, enc2: number, enc3: number, enc4: number, i: number = 0;
|
||||
while (i < input.length) {
|
||||
chr1 = input.charCodeAt(i++);
|
||||
chr2 = input.charCodeAt(i++);
|
||||
chr3 = input.charCodeAt(i++);
|
||||
|
||||
enc1 = chr1 >> 2;
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||
enc4 = chr3 & 63;
|
||||
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64;
|
||||
}
|
||||
|
||||
output.push(this._keyStr.charAt(enc1));
|
||||
output.push(this._keyStr.charAt(enc2));
|
||||
output.push(this._keyStr.charAt(enc3));
|
||||
output.push(this._keyStr.charAt(enc4));
|
||||
}
|
||||
|
||||
output = output.join("");
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析Base64格式数据
|
||||
* @param input
|
||||
* @param bytes
|
||||
*/
|
||||
static decodeBase64AsArray(input: string, bytes: number): Uint32Array {
|
||||
bytes = bytes || 1;
|
||||
|
||||
var dec = Base64Utils.decode(input), i, j, len;
|
||||
var ar: Uint32Array = new Uint32Array(dec.length / bytes);
|
||||
|
||||
for (i = 0, len = dec.length / bytes; i < len; i++) {
|
||||
ar[i] = 0;
|
||||
for (j = bytes - 1; j >= 0; --j) {
|
||||
ar[i] += dec.charCodeAt((i * bytes) + j) << (j << 3);
|
||||
}
|
||||
}
|
||||
return ar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂时不支持
|
||||
* @param data
|
||||
* @param decoded
|
||||
* @param compression
|
||||
* @private
|
||||
*/
|
||||
static decompress(data: string, decoded: any, compression: string): any {
|
||||
throw new Error("GZIP/ZLIB compressed TMX Tile Map not supported!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析csv数据
|
||||
* @param input
|
||||
*/
|
||||
static decodeCSV(input: string): Array<number> {
|
||||
var entries: Array<any> = input.replace("\n", "").trim().split(",");
|
||||
|
||||
var result:Array<number> = [];
|
||||
for (var i:number = 0; i < entries.length; i++) {
|
||||
result.push(+entries[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,43 @@
|
||||
module es {
|
||||
export class EdgeExt {
|
||||
public static oppositeEdge(self: Edge) {
|
||||
switch (self) {
|
||||
case Edge.bottom:
|
||||
return Edge.top;
|
||||
case Edge.top:
|
||||
return Edge.bottom;
|
||||
case Edge.left:
|
||||
return Edge.right;
|
||||
case Edge.right:
|
||||
return Edge.left;
|
||||
}
|
||||
}
|
||||
import { Edge } from '../../Math/Edge';
|
||||
|
||||
/**
|
||||
* 如果边是右或左,则返回true
|
||||
* @param self
|
||||
*/
|
||||
public static isHorizontal(self: Edge): boolean{
|
||||
return self == Edge.right || self == Edge.left;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果边是顶部或底部,则返回true
|
||||
* @param self
|
||||
*/
|
||||
public static isVertical(self: Edge): boolean {
|
||||
return self == Edge.top || self == Edge.bottom;
|
||||
/**
|
||||
* 边缘扩展工具类
|
||||
* 提供边缘相关的实用方法
|
||||
*/
|
||||
export class EdgeExt {
|
||||
/**
|
||||
* 获取相对的边缘
|
||||
* @param self 当前边缘
|
||||
* @returns 相对的边缘
|
||||
*/
|
||||
public static oppositeEdge(self: Edge): Edge {
|
||||
switch (self) {
|
||||
case Edge.bottom:
|
||||
return Edge.top;
|
||||
case Edge.top:
|
||||
return Edge.bottom;
|
||||
case Edge.left:
|
||||
return Edge.right;
|
||||
case Edge.right:
|
||||
return Edge.left;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查边缘是否为水平方向(左或右)
|
||||
* @param self 边缘
|
||||
* @returns 如果是水平方向返回true,否则返回false
|
||||
*/
|
||||
public static isHorizontal(self: Edge): boolean {
|
||||
return self == Edge.right || self == Edge.left;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查边缘是否为垂直方向(上或下)
|
||||
* @param self 边缘
|
||||
* @returns 如果是垂直方向返回true,否则返回false
|
||||
*/
|
||||
public static isVertical(self: Edge): boolean {
|
||||
return self == Edge.top || self == Edge.bottom;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
module es {
|
||||
export class NumberExtension {
|
||||
public static toNumber(value){
|
||||
if (value == undefined) return 0;
|
||||
|
||||
return Number(value);
|
||||
}
|
||||
/**
|
||||
* 数字扩展工具类
|
||||
* 提供数字转换的实用方法
|
||||
*/
|
||||
export class NumberExtension {
|
||||
/**
|
||||
* 将值转换为数字
|
||||
* @param value 要转换的值
|
||||
* @returns 转换后的数字,如果值为undefined则返回0
|
||||
*/
|
||||
public static toNumber(value: any): number {
|
||||
if (value == undefined) return 0;
|
||||
return Number(value);
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
module es {
|
||||
export class RandomUtils {
|
||||
/**
|
||||
* 在 start 与 stop之间取一个随机整数,可以用step指定间隔, 但不包括较大的端点(start与stop较大的一个)
|
||||
* 如
|
||||
* this.randrange(1, 10, 3)
|
||||
* 则返回的可能是 1 或 4 或 7 , 注意 这里面不会返回10,因为是10是大端点
|
||||
*
|
||||
* @param start
|
||||
* @param stop
|
||||
* @param step
|
||||
* @return 假设 start < stop, [start, stop) 区间内的随机整数
|
||||
*
|
||||
*/
|
||||
public static randrange(start: number, stop: number, step: number = 1): number {
|
||||
if (step == 0)
|
||||
throw new Error('step 不能为 0');
|
||||
|
||||
let width: number = stop - start;
|
||||
if (width == 0)
|
||||
throw new Error('没有可用的范围(' + start + ',' + stop + ')');
|
||||
if (width < 0)
|
||||
width = start - stop;
|
||||
|
||||
let n: number = Math.floor((width + step - 1) / step);
|
||||
return Math.floor(this.random() * n) * step + Math.min(start, stop);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回a 到 b之间的随机整数,包括 a 和 b
|
||||
* @param a
|
||||
* @param b
|
||||
* @return [a, b] 之间的随机整数
|
||||
*
|
||||
*/
|
||||
public static randint(a: number, b: number): number {
|
||||
a = Math.floor(a);
|
||||
b = Math.floor(b);
|
||||
if (a > b)
|
||||
a++;
|
||||
else
|
||||
b++;
|
||||
return this.randrange(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 a - b之间的随机数,不包括 Math.max(a, b)
|
||||
* @param a
|
||||
* @param b
|
||||
* @return 假设 a < b, [a, b)
|
||||
*/
|
||||
public static randnum(a: number, b: number): number {
|
||||
return this.random() * (b - a) + a;
|
||||
}
|
||||
|
||||
/**
|
||||
* 打乱数组
|
||||
* @param array
|
||||
* @return
|
||||
*/
|
||||
public static shuffle(array: any[]): any[] {
|
||||
array.sort(this._randomCompare);
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从序列中随机取一个元素
|
||||
* @param sequence 可以是 数组、 vector,等只要是有length属性,并且可以用数字索引获取元素的对象,
|
||||
* 另外,字符串也是允许的。
|
||||
* @return 序列中的某一个元素
|
||||
*
|
||||
*/
|
||||
public static choice(sequence: any): any {
|
||||
if (!sequence.hasOwnProperty("length"))
|
||||
throw new Error('无法对此对象执行此操作');
|
||||
let index: number = Math.floor(this.random() * sequence.length);
|
||||
if (sequence instanceof String)
|
||||
return String(sequence).charAt(index);
|
||||
else
|
||||
return sequence[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* 对列表中的元素进行随机采æ ?
|
||||
* <pre>
|
||||
* this.sample([1, 2, 3, 4, 5], 3) // Choose 3 elements
|
||||
* [4, 1, 5]
|
||||
* </pre>
|
||||
* @param sequence
|
||||
* @param num
|
||||
* @return
|
||||
*
|
||||
*/
|
||||
public static sample(sequence: any[], num: number): any[] {
|
||||
let len: number = sequence.length;
|
||||
if (num <= 0 || len < num)
|
||||
throw new Error("采样数量不够");
|
||||
|
||||
let selected: any[] = [];
|
||||
let indices: any[] = [];
|
||||
for (let i: number = 0; i < num; i++) {
|
||||
let index: number = Math.floor(this.random() * len);
|
||||
while (indices.indexOf(index) >= 0)
|
||||
index = Math.floor(this.random() * len);
|
||||
|
||||
selected.push(sequence[index]);
|
||||
indices.push(index);
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回 0.0 - 1.0 之间的随机数,等同于 Math.random()
|
||||
* @return Math.random()
|
||||
*
|
||||
*/
|
||||
public static random(): number {
|
||||
return Math.random();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算概率
|
||||
* @param chance 概率
|
||||
* @return
|
||||
*/
|
||||
public static boolean(chance: number = .5): boolean {
|
||||
return (this.random() < chance) ? true : false;
|
||||
}
|
||||
|
||||
private static _randomCompare(a: Object, b: Object): number {
|
||||
return (this.random() > .5) ? 1 : -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,396 +0,0 @@
|
||||
module es {
|
||||
export class RectangleExt {
|
||||
/**
|
||||
* 获取指定边的位置
|
||||
* @param rect
|
||||
* @param edge
|
||||
*/
|
||||
public static getSide(rect: Rectangle, edge: Edge) {
|
||||
switch (edge) {
|
||||
case Edge.top:
|
||||
return rect.top;
|
||||
case Edge.bottom:
|
||||
return rect.bottom;
|
||||
case es.Edge.left:
|
||||
return rect.left;
|
||||
case Edge.right:
|
||||
return rect.right;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个矩形的并集。结果将是一个包含其他两个的矩形。
|
||||
* @param first
|
||||
* @param point
|
||||
*/
|
||||
public static union(first: Rectangle, point: Vector2) {
|
||||
let rect = new Rectangle(point.x, point.y, 0, 0);
|
||||
let result = new Rectangle();
|
||||
result.x = Math.min(first.x, rect.x);
|
||||
result.y = Math.min(first.y, rect.y);
|
||||
result.width = Math.max(first.right, rect.right) - result.x;
|
||||
result.height = Math.max(first.bottom, rect.bottom) - result.y;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static getHalfRect(rect: Rectangle, edge: Edge) {
|
||||
switch (edge) {
|
||||
case Edge.top:
|
||||
return new Rectangle(rect.x, rect.y, rect.width, rect.height / 2);
|
||||
case Edge.bottom:
|
||||
return new Rectangle(rect.x, rect.y + rect.height / 2, rect.width, rect.height / 2);
|
||||
case Edge.left:
|
||||
return new Rectangle(rect.x, rect.y, rect.width / 2, rect.height);
|
||||
case Edge.right:
|
||||
return new Rectangle(rect.x + rect.width / 2, rect.y, rect.width / 2, rect.height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取矩形的一部分,其宽度/高度的大小位于矩形的边缘,但仍然包含在其中。
|
||||
* @param rect
|
||||
* @param edge
|
||||
* @param size
|
||||
*/
|
||||
public static getRectEdgePortion(rect: Rectangle, edge: Edge, size: number = 1) {
|
||||
switch (edge) {
|
||||
case es.Edge.top:
|
||||
return new Rectangle(rect.x, rect.y, rect.width, size);
|
||||
case Edge.bottom:
|
||||
return new Rectangle(rect.x, rect.y + rect.height - size, rect.width, size);
|
||||
case Edge.left:
|
||||
return new Rectangle(rect.x, rect.y, size, rect.height);
|
||||
case Edge.right:
|
||||
return new Rectangle(rect.x + rect.width - size, rect.y, size, rect.height);
|
||||
}
|
||||
}
|
||||
|
||||
public static expandSide(rect: Rectangle, edge: Edge, amount: number) {
|
||||
amount = Math.abs(amount);
|
||||
|
||||
switch (edge) {
|
||||
case Edge.top:
|
||||
rect.y -= amount;
|
||||
rect.height += amount;
|
||||
break;
|
||||
case es.Edge.bottom:
|
||||
rect.height += amount;
|
||||
break;
|
||||
case Edge.left:
|
||||
rect.x -= amount;
|
||||
rect.width += amount;
|
||||
break;
|
||||
case Edge.right:
|
||||
rect.width += amount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static contract(rect: Rectangle, horizontalAmount, verticalAmount) {
|
||||
rect.x += horizontalAmount;
|
||||
rect.y += verticalAmount;
|
||||
rect.width -= horizontalAmount * 2;
|
||||
rect.height -= verticalAmount * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定多边形的点,计算其边界
|
||||
* @param points
|
||||
*/
|
||||
public static boundsFromPolygonVector(points: Vector2[]) {
|
||||
// 我们需要找到最小/最大的x/y值。
|
||||
let minX = Number.POSITIVE_INFINITY;
|
||||
let minY = Number.POSITIVE_INFINITY;
|
||||
let maxX = Number.NEGATIVE_INFINITY;
|
||||
let maxY = Number.NEGATIVE_INFINITY;
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
let pt = points[i];
|
||||
|
||||
if (pt.x < minX)
|
||||
minX = pt.x;
|
||||
if (pt.x > maxX)
|
||||
maxX = pt.x;
|
||||
|
||||
if (pt.y < minY)
|
||||
minY = pt.y;
|
||||
if (pt.y > maxY)
|
||||
maxY = pt.y;
|
||||
}
|
||||
|
||||
return this.fromMinMaxVector(new Vector2(minX, minY), new Vector2(maxX, maxY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个给定最小/最大点(左上角,右下角)的矩形
|
||||
* @param min
|
||||
* @param max
|
||||
*/
|
||||
public static fromMinMaxVector(min: Vector2, max: Vector2) {
|
||||
return new Rectangle(min.x, min.y, max.x - min.x, max.y - min.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个跨越当前边界和提供的delta位置的Bounds
|
||||
* @param rect
|
||||
* @param deltaX
|
||||
* @param deltaY
|
||||
*/
|
||||
public static getSweptBroadphaseBounds(rect: Rectangle, deltaX: number, deltaY: number) {
|
||||
let broadphasebox = Rectangle.empty;
|
||||
|
||||
broadphasebox.x = deltaX > 0 ? rect.x : rect.x + deltaX;
|
||||
broadphasebox.y = deltaY > 0 ? rect.y : rect.y + deltaY;
|
||||
broadphasebox.width = deltaX > 0 ? deltaX + rect.width : rect.width - deltaX;
|
||||
broadphasebox.height = deltaY > 0 ? deltaY + rect.height : rect.height - deltaY;
|
||||
|
||||
return broadphasebox;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果矩形发生碰撞,返回true
|
||||
* moveX和moveY将返回b1为避免碰撞而必须移动的移动量
|
||||
* @param rect
|
||||
* @param other
|
||||
* @param moveX
|
||||
* @param moveY
|
||||
*/
|
||||
public collisionCheck(rect: Rectangle, other: Rectangle, moveX: Ref<number>, moveY: Ref<number>) {
|
||||
moveX.value = moveY.value = 0;
|
||||
|
||||
let l = other.x - (rect.x + rect.width);
|
||||
let r = (other.x + other.width) - rect.x;
|
||||
let t = other.y - (rect.y + rect.height);
|
||||
let b = (other.y + other.height) - rect.y;
|
||||
|
||||
// 检验是否有碰撞
|
||||
if (l > 0 || r < 0 || t > 0 || b < 0)
|
||||
return false;
|
||||
|
||||
// 求两边的偏移量
|
||||
moveX.value = Math.abs(l) < r ? l : r;
|
||||
moveY.value = Math.abs(t) < b ? t : b;
|
||||
|
||||
// 只使用最小的偏移量
|
||||
if (Math.abs(moveX.value) < Math.abs(moveY.value))
|
||||
moveY.value = 0;
|
||||
else
|
||||
moveX.value = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个矩形之间有符号的交点深度
|
||||
* @param rectA
|
||||
* @param rectB
|
||||
* @returns 两个相交的矩形之间的重叠量。
|
||||
* 这些深度值可以是负值,取决于矩形相交的边。
|
||||
* 这允许调用者确定正确的推送对象的方向,以解决碰撞问题。
|
||||
* 如果矩形不相交,则返回Vector2.zero。
|
||||
*/
|
||||
public static getIntersectionDepth(rectA: Rectangle, rectB: Rectangle) {
|
||||
// 计算半尺寸
|
||||
let halfWidthA = rectA.width / 2;
|
||||
let halfHeightA = rectA.height / 2;
|
||||
let halfWidthB = rectB.width / 2;
|
||||
let halfHeightB = rectB.height / 2;
|
||||
|
||||
// 计算中心
|
||||
let centerA = new Vector2(rectA.left + halfWidthA, rectA.top + halfHeightA);
|
||||
let centerB = new Vector2(rectB.left + halfWidthB, rectB.top + halfHeightB);
|
||||
|
||||
// 计算当前中心间的距离和最小非相交距离
|
||||
let distanceX = centerA.x - centerB.x;
|
||||
let distanceY = centerA.y - centerB.y;
|
||||
let minDistanceX = halfWidthA + halfWidthB;
|
||||
let minDistanceY = halfHeightA + halfHeightB;
|
||||
|
||||
// 如果我们根本不相交,则返回(0,0)
|
||||
if (Math.abs(distanceX) >= minDistanceX || Math.abs(distanceY) >= minDistanceY)
|
||||
return Vector2.zero;
|
||||
|
||||
// 计算并返回交叉点深度
|
||||
let depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
|
||||
let depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
|
||||
|
||||
return new Vector2(depthX, depthY);
|
||||
}
|
||||
|
||||
public static getClosestPointOnBoundsToOrigin(rect: Rectangle) {
|
||||
let max = this.getMax(rect);
|
||||
let minDist = Math.abs(rect.location.x);
|
||||
let boundsPoint = new Vector2(rect.location.x, 0);
|
||||
|
||||
if (Math.abs(max.x) < minDist) {
|
||||
minDist = Math.abs(max.x);
|
||||
boundsPoint.x = max.x;
|
||||
boundsPoint.y = 0;
|
||||
}
|
||||
|
||||
if (Math.abs(max.y) < minDist) {
|
||||
minDist = Math.abs(max.y);
|
||||
boundsPoint.x = 0;
|
||||
boundsPoint.y = max.y;
|
||||
}
|
||||
|
||||
if (Math.abs(rect.location.y) < minDist) {
|
||||
minDist = Math.abs(rect.location.y);
|
||||
boundsPoint.x = 0;
|
||||
boundsPoint.y = rect.location.y;
|
||||
}
|
||||
|
||||
return boundsPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Rectangle中或上的最接近点返回给定点
|
||||
* @param rect
|
||||
* @param point
|
||||
*/
|
||||
public static getClosestPointOnRectangleToPoint(rect: Rectangle, point: Vector2) {
|
||||
// 对于每个轴,如果该点在盒子外面,则将在盒子上,否则不理会它
|
||||
let res = es.Vector2.zero;
|
||||
res.x = MathHelper.clamp(point.x, rect.left, rect.right)
|
||||
res.y = MathHelper.clamp(point.y, rect.top, rect.bottom);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取矩形边界上与给定点最接近的点
|
||||
* @param rect
|
||||
* @param point
|
||||
*/
|
||||
public static getClosestPointOnRectangleBorderToPoint(rect: Rectangle, point: Vector2) {
|
||||
// 对于每个轴,如果该点在盒子外面,则将在盒子上,否则不理会它
|
||||
let res = es.Vector2.zero;
|
||||
res.x = MathHelper.clamp(MathHelper.toInt(point.x), rect.left, rect.right)
|
||||
res.y = MathHelper.clamp(MathHelper.toInt(point.y), rect.top, rect.bottom);
|
||||
|
||||
// 如果点在矩形内,我们需要将res推到边框,因为它将在矩形内
|
||||
if (rect.contains(res.x, res.y)) {
|
||||
let dl = rect.x - rect.left;
|
||||
let dr = rect.right - res.x;
|
||||
let dt = res.y - rect.top;
|
||||
let db = rect.bottom - res.y;
|
||||
|
||||
let min = Math.min(dl, dr, dt, db);
|
||||
if (min == dt)
|
||||
res.y = rect.top;
|
||||
else if (min == db)
|
||||
res.y = rect.bottom;
|
||||
else if (min == dl)
|
||||
res.x == rect.left;
|
||||
else
|
||||
res.x = rect.right;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static getMax(rect: Rectangle) {
|
||||
return new Vector2(rect.right, rect.bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* 以Vector2的形式获取矩形的中心点
|
||||
* @param rect
|
||||
* @returns
|
||||
*/
|
||||
public static getCenter(rect: Rectangle) {
|
||||
return new Vector2(rect.x + rect.width / 2, rect.y + rect.height / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定多边形的点即可计算边界
|
||||
* @param points
|
||||
*/
|
||||
public static boundsFromPolygonPoints(points: Vector2[]) {
|
||||
// 我们需要找到最小/最大x / y值
|
||||
let minX = Number.POSITIVE_INFINITY;
|
||||
let minY = Number.POSITIVE_INFINITY;
|
||||
let maxX = Number.NEGATIVE_INFINITY;
|
||||
let maxY = Number.NEGATIVE_INFINITY;
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
let pt = points[i];
|
||||
|
||||
if (pt.x < minX)
|
||||
minX = pt.x;
|
||||
if (pt.x > maxX)
|
||||
maxX = pt.x
|
||||
|
||||
if (pt.y < minY)
|
||||
minY = pt.y;
|
||||
if (pt.y > maxY)
|
||||
maxY = pt.y;
|
||||
}
|
||||
|
||||
return this.fromMinMaxVector(new Vector2(MathHelper.toInt(minX), MathHelper.toInt(minY)), new Vector2(MathHelper.toInt(maxX), MathHelper.toInt(maxY)));
|
||||
}
|
||||
|
||||
public static calculateBounds(rect: Rectangle, parentPosition: Vector2, position: Vector2, origin: Vector2, scale: Vector2,
|
||||
rotation: number, width: number, height: number) {
|
||||
if (rotation == 0) {
|
||||
rect.x = MathHelper.toInt(parentPosition.x + position.x - origin.x * scale.x);
|
||||
rect.y = MathHelper.toInt(parentPosition.y + position.y - origin.y * scale.y);
|
||||
rect.width = MathHelper.toInt(width * scale.x);
|
||||
rect.height = MathHelper.toInt(height * scale.y);
|
||||
} else {
|
||||
// 我们需要找到我们的绝对最小/最大值,并据此创建边界
|
||||
let worldPosX = parentPosition.x + position.x;
|
||||
let worldPosY = parentPosition.y + position.y;
|
||||
|
||||
let tempMat: Matrix2D;
|
||||
|
||||
// 考虑到原点,将参考点设置为世界参考
|
||||
let transformMatrix = new Matrix2D();
|
||||
Matrix2D.createTranslation(-worldPosX - origin.x, -worldPosY - origin.y, transformMatrix);
|
||||
Matrix2D.createScale(scale.x, scale.y, tempMat);
|
||||
transformMatrix = transformMatrix.multiply(tempMat);
|
||||
Matrix2D.createRotation(rotation, tempMat);
|
||||
transformMatrix =transformMatrix.multiply(tempMat);
|
||||
Matrix2D.createTranslation(worldPosX, worldPosY, tempMat);
|
||||
transformMatrix = transformMatrix.multiply(tempMat);
|
||||
|
||||
// TODO: 我们可以把世界变换留在矩阵中,避免在世界空间中得到所有的四个角
|
||||
let topLeft = new Vector2(worldPosX, worldPosY);
|
||||
let topRight = new Vector2(worldPosX + width, worldPosY);
|
||||
let bottomLeft = new Vector2(worldPosX, worldPosY + height);
|
||||
let bottomRight = new Vector2(worldPosX + width, worldPosY + height);
|
||||
|
||||
Vector2Ext.transformR(topLeft, transformMatrix, topLeft);
|
||||
Vector2Ext.transformR(topRight, transformMatrix, topRight);
|
||||
Vector2Ext.transformR(bottomLeft, transformMatrix, bottomLeft);
|
||||
Vector2Ext.transformR(bottomRight, transformMatrix, bottomRight);
|
||||
|
||||
// 找出最小值和最大值,这样我们就可以计算出我们的边界框。
|
||||
let minX = MathHelper.toInt(Math.min(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x));
|
||||
let maxX = MathHelper.toInt(Math.max(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x));
|
||||
let minY = MathHelper.toInt(Math.min(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y));
|
||||
let maxY = MathHelper.toInt(Math.max(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y));
|
||||
|
||||
rect.location = new Vector2(minX, minY);
|
||||
rect.width = MathHelper.toInt(maxX - minX);
|
||||
rect.height = MathHelper.toInt(maxY - minY);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放矩形
|
||||
* @param rect
|
||||
* @param scale
|
||||
*/
|
||||
public static scale(rect: Rectangle, scale: Vector2) {
|
||||
rect.x = MathHelper.toInt(rect.x * scale.x);
|
||||
rect.y = MathHelper.toInt(rect.y * scale.y);
|
||||
rect.width = MathHelper.toInt(rect.width * scale.x);
|
||||
rect.height = MathHelper.toInt(rect.height * scale.y);
|
||||
}
|
||||
|
||||
public static translate(rect: Rectangle, vec: Vector2) {
|
||||
rect.location.addEqual(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
module es {
|
||||
export class TextureUtils {
|
||||
public static premultiplyAlpha(pixels: number[]) {
|
||||
let b = pixels[0];
|
||||
for (let i = 0; i < pixels.length; i += 4) {
|
||||
if (b[i + 3] != 255) {
|
||||
let alpha = b[i + 3] / 255;
|
||||
b[i + 0] = b[i + 0] * alpha;
|
||||
b[i + 1] = b[i + 1] * alpha;
|
||||
b[i + 2] = b[i + 2] * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
module es {
|
||||
export class TypeUtils {
|
||||
public static getType(obj: any){
|
||||
return obj.constructor;
|
||||
}
|
||||
/**
|
||||
* 类型工具类
|
||||
* 提供类型相关的实用方法
|
||||
*/
|
||||
export class TypeUtils {
|
||||
/**
|
||||
* 获取对象的类型
|
||||
* @param obj 对象
|
||||
* @returns 对象的构造函数
|
||||
*/
|
||||
public static getType(obj: any) {
|
||||
return obj.constructor;
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
module es {
|
||||
export class Vector2Ext {
|
||||
/**
|
||||
* 检查三角形是CCW还是CW
|
||||
* @param a
|
||||
* @param center
|
||||
* @param c
|
||||
*/
|
||||
public static isTriangleCCW(a: Vector2, center: Vector2, c: Vector2) {
|
||||
return this.cross(center.sub(a), c.sub(center)) < 0;
|
||||
}
|
||||
|
||||
public static halfVector(): Vector2 {
|
||||
return new Vector2(0.5, 0.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算二维伪叉乘点(Perp(u), v)
|
||||
* @param u
|
||||
* @param v
|
||||
*/
|
||||
public static cross(u: Vector2, v: Vector2) {
|
||||
return u.y * v.x - u.x * v.y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回垂直于传入向量的向量
|
||||
* @param first
|
||||
* @param second
|
||||
*/
|
||||
public static perpendicular(first: Vector2, second: Vector2) {
|
||||
return new Vector2(-1 * (second.y - first.y), second.x - first.x);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将x/y值翻转,并将y反转,得到垂直于x/y的值
|
||||
* @param original
|
||||
*/
|
||||
public static perpendicularFlip(original: Vector2) {
|
||||
return new Vector2(-original.y, original.x);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回两个向量之间的角度,单位为度
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
public static angle(from: Vector2, to: Vector2) {
|
||||
this.normalize(from);
|
||||
this.normalize(to);
|
||||
return Math.acos(MathHelper.clamp(from.dot(to), -1, 1)) * MathHelper.Rad2Deg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回以自度为中心的左右角度
|
||||
* @param self
|
||||
* @param left
|
||||
* @param right
|
||||
*/
|
||||
public static angleBetween(self: Vector2, left: Vector2, right: Vector2) {
|
||||
const one = left.sub(self);
|
||||
const two = right.sub(self);
|
||||
return this.angle(one, two);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定两条直线(ab和cd),求交点
|
||||
* @param a
|
||||
* @param b
|
||||
* @param c
|
||||
* @param d
|
||||
* @param intersection
|
||||
*/
|
||||
public static getRayIntersection(a: Vector2, b: Vector2, c: Vector2, d: Vector2, intersection: Vector2 = es.Vector2.zero) {
|
||||
let dy1 = b.y - a.y;
|
||||
let dx1 = b.x - a.x;
|
||||
let dy2 = d.y - c.y;
|
||||
let dx2 = d.x - c.x;
|
||||
|
||||
if (dy1 * dx2 == dy2 * dx1) {
|
||||
intersection.x = Number.NaN;
|
||||
intersection.y = Number.NaN;
|
||||
return false;
|
||||
}
|
||||
|
||||
let x = ((c.y - a.y) * dx1 * dx2 + dy1 * dx2 * a.x - dy2 * dx1 * c.x) / (dy1 * dx2 - dy2 * dx1);
|
||||
let y = a.y + (dy1 / dx1) * (x - a.x);
|
||||
|
||||
intersection.x = x;
|
||||
intersection.y = y;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vector2的临时解决方案
|
||||
* 标准化把向量弄乱了
|
||||
* @param vec
|
||||
*/
|
||||
public static normalize(vec: Vector2) {
|
||||
let magnitude = Math.sqrt((vec.x * vec.x) + (vec.y * vec.y));
|
||||
if (magnitude > MathHelper.Epsilon) {
|
||||
vec.divideScaler(magnitude);
|
||||
} else {
|
||||
vec.x = vec.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过指定的矩阵对Vector2的数组中的向量应用变换,并将结果放置在另一个数组中。
|
||||
* @param sourceArray
|
||||
* @param sourceIndex
|
||||
* @param matrix
|
||||
* @param destinationArray
|
||||
* @param destinationIndex
|
||||
* @param length
|
||||
*/
|
||||
public static transformA(sourceArray: Vector2[], sourceIndex: number, matrix: Matrix2D,
|
||||
destinationArray: Vector2[], destinationIndex: number, length: number) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
let position = sourceArray[sourceIndex + i];
|
||||
let destination = destinationArray[destinationIndex + i];
|
||||
destination.x = (position.x * matrix.m11) + (position.y * matrix.m21) + matrix.m31;
|
||||
destination.y = (position.x * matrix.m12) + (position.y * matrix.m22) + matrix.m32;
|
||||
destinationArray[destinationIndex + i] = destination;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个新的Vector2,该Vector2包含了通过指定的Matrix进行的二维向量变换
|
||||
* @param position
|
||||
* @param matrix
|
||||
* @param result
|
||||
*/
|
||||
public static transformR(position: Vector2, matrix: Matrix2D, result: Vector2 = es.Vector2.zero) {
|
||||
let x = (position.x * matrix.m11) + (position.y * matrix.m21) + matrix.m31;
|
||||
let y = (position.x * matrix.m12) + (position.y * matrix.m22) + matrix.m32;
|
||||
result.x = x;
|
||||
result.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过指定的矩阵对Vector2的数组中的所有向量应用变换,并将结果放到另一个数组中。
|
||||
* @param sourceArray
|
||||
* @param matrix
|
||||
* @param destinationArray
|
||||
*/
|
||||
public static transform(sourceArray: Vector2[], matrix: Matrix2D, destinationArray: Vector2[]) {
|
||||
this.transformA(sourceArray, 0, matrix, destinationArray, 0, sourceArray.length);
|
||||
}
|
||||
|
||||
public static round(vec: Vector2) {
|
||||
return new Vector2(Math.round(vec.x), Math.round(vec.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
4
source/src/Utils/Extensions/index.ts
Normal file
4
source/src/Utils/Extensions/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// 扩展工具类导出
|
||||
export { TypeUtils } from './TypeUtils';
|
||||
export { NumberExtension } from './NumberExtension';
|
||||
export { EdgeExt } from './EdgeExt';
|
||||
@@ -1,58 +1,55 @@
|
||||
module es {
|
||||
/**
|
||||
* 全局管理器的基类。所有全局管理器都应该从此类继承。
|
||||
*/
|
||||
export class GlobalManager {
|
||||
/**
|
||||
* 全局管理器的基类。所有全局管理器都应该从此类继承。
|
||||
* 表示管理器是否启用
|
||||
*/
|
||||
export class GlobalManager {
|
||||
/**
|
||||
* 表示管理器是否启用
|
||||
*/
|
||||
public _enabled: boolean;
|
||||
public _enabled: boolean = false;
|
||||
|
||||
/**
|
||||
* 获取或设置管理器是否启用
|
||||
*/
|
||||
public get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
/**
|
||||
* 获取或设置管理器是否启用
|
||||
*/
|
||||
public get enabled() {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
public set enabled(value: boolean) {
|
||||
this.setEnabled(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置管理器是否启用
|
||||
* @param isEnabled 如果为true,则启用管理器;否则禁用管理器
|
||||
*/
|
||||
public setEnabled(isEnabled: boolean) {
|
||||
if (this._enabled != isEnabled) {
|
||||
this._enabled = isEnabled;
|
||||
if (this._enabled) {
|
||||
// 如果启用了管理器,则调用onEnabled方法
|
||||
this.onEnabled();
|
||||
} else {
|
||||
// 如果禁用了管理器,则调用onDisabled方法
|
||||
this.onDisabled();
|
||||
}
|
||||
/**
|
||||
* 设置管理器是否启用
|
||||
* @param isEnabled 如果为true,则启用管理器;否则禁用管理器
|
||||
*/
|
||||
public setEnabled(isEnabled: boolean) {
|
||||
if (this._enabled != isEnabled) {
|
||||
this._enabled = isEnabled;
|
||||
if (this._enabled) {
|
||||
// 如果启用了管理器,则调用onEnabled方法
|
||||
this.onEnabled();
|
||||
} else {
|
||||
// 如果禁用了管理器,则调用onDisabled方法
|
||||
this.onDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在启用管理器时调用的回调方法
|
||||
*/
|
||||
public onEnabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在禁用管理器时调用的回调方法
|
||||
*/
|
||||
public onDisabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新管理器状态的方法
|
||||
*/
|
||||
public update() {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在启用管理器时调用的回调方法
|
||||
*/
|
||||
protected onEnabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在禁用管理器时调用的回调方法
|
||||
*/
|
||||
protected onDisabled() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新管理器状态的方法
|
||||
*/
|
||||
public update() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
module es {
|
||||
export class Hash {
|
||||
/**
|
||||
* 从一个字节数组中计算一个哈希值
|
||||
* @param data
|
||||
*/
|
||||
public static computeHash(...data: number[]) {
|
||||
const p: number = 16777619;
|
||||
let hash = 2166136261;
|
||||
|
||||
for (let i = 0; i < data.length; i++)
|
||||
hash = (hash ^ data[i]) * p;
|
||||
|
||||
hash += hash << 13;
|
||||
hash ^= hash >> 7;
|
||||
hash += hash << 3;
|
||||
hash ^= hash >> 17;
|
||||
hash += hash << 5;
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
module es {
|
||||
export interface IComparer<T>{
|
||||
compare(x: T, y: T): number;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 对象声明自己的平等方法和Hashcode的生成
|
||||
*/
|
||||
export interface IEqualityComparable {
|
||||
/**
|
||||
* 确定另一个对象是否等于这个实例
|
||||
* @param other
|
||||
*/
|
||||
equals(other: any): boolean;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 为确定对象的哈希码和两个项目是否相等提供接口
|
||||
*/
|
||||
export interface IEqualityComparer<T> {
|
||||
/**
|
||||
* 判断两个对象是否相等
|
||||
* @param x
|
||||
* @param y
|
||||
*/
|
||||
equals(x: T, y: T): boolean;
|
||||
|
||||
/**
|
||||
* 生成对象的哈希码
|
||||
* @param value
|
||||
*/
|
||||
getHashCode(value: T): number;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 实现该接口用于判定两个对象是否相等的快速接口
|
||||
*/
|
||||
export interface IEquatable<T> {
|
||||
equals(other: T): boolean;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
module es {
|
||||
export class Enumerable {
|
||||
/**
|
||||
* 在指定范围内生成一个整数序列。
|
||||
*/
|
||||
public static range(start: number, count: number): List<number> {
|
||||
let result = new List<number>();
|
||||
while (count--) {
|
||||
result.add(start++)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成包含一个重复值的序列。
|
||||
*/
|
||||
public static repeat<T>(element: T, count: number): List<T> {
|
||||
let result = new List<T>();
|
||||
while (count--) {
|
||||
result.add(element)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 检查传递的参数是否为对象
|
||||
*/
|
||||
export const isObj = <T>(x: T): boolean => !!x && typeof x === 'object';
|
||||
|
||||
/**
|
||||
* 创建一个否定谓词结果的函数
|
||||
*/
|
||||
export const negate = <T>(
|
||||
pred: (...args: T[]) => boolean
|
||||
): ((...args: T[]) => boolean) => (...args) => !pred(...args);
|
||||
|
||||
/**
|
||||
* 比较器助手
|
||||
*/
|
||||
|
||||
export const composeComparers = <T>(
|
||||
previousComparer: (a: T, b: T) => number,
|
||||
currentComparer: (a: T, b: T) => number
|
||||
): ((a: T, b: T) => number) => (a: T, b: T) =>
|
||||
previousComparer(a, b) || currentComparer(a, b);
|
||||
|
||||
export const keyComparer = <T>(
|
||||
_keySelector: (key: T) => string,
|
||||
descending?: boolean
|
||||
): ((a: T, b: T) => number) => (a: T, b: T) => {
|
||||
const sortKeyA = _keySelector(a);
|
||||
const sortKeyB = _keySelector(b);
|
||||
if (sortKeyA > sortKeyB) {
|
||||
return !descending ? 1 : -1
|
||||
} else if (sortKeyA < sortKeyB) {
|
||||
return !descending ? -1 : 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,791 +0,0 @@
|
||||
module es {
|
||||
type PredicateType<T> = (value?: T, index?: number, list?: T[]) => boolean
|
||||
|
||||
export class List<T> {
|
||||
protected _elements: T[];
|
||||
|
||||
/**
|
||||
* 默认为列表的元素
|
||||
*/
|
||||
constructor(elements: T[] = []) {
|
||||
this._elements = elements
|
||||
}
|
||||
|
||||
/**
|
||||
* 在列表的末尾添加一个对象。
|
||||
*/
|
||||
public add(element: T): void {
|
||||
this._elements.push(element)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将一个对象追加到列表的末尾。
|
||||
*/
|
||||
public append(element: T): void {
|
||||
this.add(element)
|
||||
}
|
||||
|
||||
/**
|
||||
* 在列表的开头添加一个对象。
|
||||
*/
|
||||
public prepend(element: T): void {
|
||||
this._elements.unshift(element)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定集合的元素添加到列表的末尾。
|
||||
*/
|
||||
public addRange(elements: T[]): void {
|
||||
this._elements.push(...elements)
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用指定的累加器函数将数组中的所有元素聚合成一个值。
|
||||
* @param accumulator 用于计算聚合值的累加器函数。
|
||||
* @param initialValue 可选参数,用于指定累加器函数的初始值。
|
||||
* @returns 聚合后的值。
|
||||
*/
|
||||
public aggregate<U>(
|
||||
accumulator: (accum: U, value?: T, index?: number, list?: T[]) => any,
|
||||
initialValue?: U
|
||||
): any {
|
||||
return this._elements.reduce(accumulator, initialValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列表中的所有元素是否都满足指定条件
|
||||
* @param predicate 谓词函数,用于对列表中的每个元素进行评估
|
||||
* @returns {boolean} 如果列表中的所有元素都满足条件,则返回 true;否则返回 false
|
||||
*/
|
||||
public all(predicate: PredicateType<T>): boolean {
|
||||
// 调用 every 方法,传入谓词函数,检查列表中的所有元素是否都满足条件
|
||||
return this._elements.every(predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 该方法用于判断数组中是否存在元素
|
||||
* @param predicate 可选参数,用于检查是否有至少一个元素满足该函数
|
||||
* @returns 如果存在元素,返回 true;如果不存在元素,返回 false
|
||||
*/
|
||||
public any(predicate?: (element: T) => boolean): boolean {
|
||||
// 如果 predicate 函数提供了,则使用 some() 方法判断是否有任意元素满足该函数
|
||||
if (predicate) {
|
||||
return this._elements.some(predicate);
|
||||
}
|
||||
// 如果没有提供 predicate 函数,则检查数组的长度是否大于 0
|
||||
return this._elements.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算数组中所有元素的平均值
|
||||
* @param transform 可选参数,用于将数组中的每个元素转换成另外的值进行计算
|
||||
* @returns 数组的平均值
|
||||
*/
|
||||
public average(
|
||||
transform?: (value?: T, index?: number, list?: T[]) => any
|
||||
): number {
|
||||
// 调用 sum() 方法计算数组中所有元素的和
|
||||
const sum = this.sum(transform);
|
||||
// 调用 count() 方法计算数组中元素的个数
|
||||
const count = this.count(transform);
|
||||
// 如果元素的个数为 0,则返回 NaN
|
||||
if (count === 0) {
|
||||
return NaN;
|
||||
}
|
||||
// 计算数组的平均值并返回
|
||||
return sum / count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将序列的元素转换为指定的类型。
|
||||
*/
|
||||
public cast<U>(): List<U> {
|
||||
return new List<U>(this._elements as any)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从列表中删除所有元素。
|
||||
*/
|
||||
public clear(): void {
|
||||
this._elements.length = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接两个序列。
|
||||
*/
|
||||
public concat(list: List<T>): List<T> {
|
||||
return new List<T>(this._elements.concat(list.toArray()))
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定一个元素是否在列表中。
|
||||
*/
|
||||
public contains(element: T): boolean {
|
||||
return this.any(x => x === element)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算数组中所有元素的数量,或者根据指定的条件计算符合条件的元素的数量。
|
||||
* @param predicate 可选参数,用于过滤元素的条件函数。
|
||||
* @returns 数组元素的数量。
|
||||
*/
|
||||
public count(): number
|
||||
public count(predicate: PredicateType<T>): number
|
||||
public count(predicate?: PredicateType<T>): number {
|
||||
return predicate ? this.where(predicate).count() : this._elements.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前数组,如果当前数组为空,则返回一个只包含默认值的新数组。
|
||||
* @param defaultValue 默认值。
|
||||
* @returns 当前数组,或者只包含默认值的新数组。
|
||||
*/
|
||||
public defaultIfEmpty(defaultValue?: T): List<T> {
|
||||
return this.count() ? this : new List<T>([defaultValue]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的键选择器从数组中去除重复的元素。
|
||||
* @param keySelector 用于选择每个元素的键的函数。
|
||||
* @returns 去重后的数组。
|
||||
*/
|
||||
public distinctBy(keySelector: (key: T) => string | number): List<T> {
|
||||
const groups = this.groupBy(keySelector); // 根据键选择器对数组进行分组。
|
||||
return Object.keys(groups).reduce((res, key) => { // 遍历分组后的对象。
|
||||
res.add(groups[key][0] as T); // 将每组的第一个元素加入结果集合。
|
||||
return res;
|
||||
}, new List<T>()); // 返回结果集合。
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的索引获取数组中的元素
|
||||
* @param index 要获取的元素的索引
|
||||
* @returns 数组中的元素
|
||||
* @throws {Error} 如果索引小于 0 或大于等于数组长度,则抛出 "ArgumentOutOfRangeException" 异常。
|
||||
*/
|
||||
public elementAt(index: number): T {
|
||||
if (index < this.count() && index >= 0) {
|
||||
return this._elements[index];
|
||||
} else {
|
||||
throw new Error(
|
||||
'ArgumentOutOfRangeException: index is less than 0 or greater than or equal to the number of elements in source.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定索引处的元素,如果索引超出数组范围,则返回 null。
|
||||
* @param index 索引。
|
||||
* @returns 指定索引处的元素,如果索引超出数组范围,则返回 null。
|
||||
*/
|
||||
public elementAtOrDefault(index: number): T | null {
|
||||
return index < this.count() && index >= 0 ? this._elements[index] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前数组中不在指定数组中的元素集合。
|
||||
* @param source 指定数组。
|
||||
* @returns 当前数组中不在指定数组中的元素集合。
|
||||
*/
|
||||
public except(source: List<T>): List<T> {
|
||||
return this.where(x => !source.contains(x));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前数组中第一个元素,或者符合条件的第一个元素。
|
||||
* @param predicate 符合条件的判断函数。
|
||||
* @returns 当前数组中第一个元素,或者符合条件的第一个元素。
|
||||
*/
|
||||
public first(): T;
|
||||
public first(predicate: PredicateType<T>): T;
|
||||
public first(predicate?: PredicateType<T>): T {
|
||||
if (this.count()) {
|
||||
return predicate ? this.where(predicate).first() : this._elements[0];
|
||||
} else {
|
||||
throw new Error('InvalidOperationException: The source sequence is empty.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的条件查询数组中第一个符合条件的元素,如果不存在符合条件的元素,则返回默认值 null 或 undefined。
|
||||
* @param predicate 可选参数,表示查询条件的谓词函数
|
||||
* @returns 符合条件的元素或默认值 null 或 undefined
|
||||
*/
|
||||
public firstOrDefault(): T
|
||||
public firstOrDefault(predicate: PredicateType<T>): T
|
||||
public firstOrDefault(predicate?: PredicateType<T>): T {
|
||||
return this.count(predicate) ? this.first(predicate) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对数组中的每个元素执行指定的操作
|
||||
* @param action 要执行的操作,可以是一个函数或函数表达式
|
||||
*/
|
||||
public forEach(action: (value?: T, index?: number, list?: T[]) => any): void {
|
||||
return this._elements.forEach(action);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的键对数组元素进行分组,并返回一个包含分组结果的对象
|
||||
* @param grouper 指定的键,用于分组
|
||||
* @param mapper 可选参数,用于对分组后的每个元素进行转换的函数
|
||||
* @returns 包含分组结果的对象,其中键为分组后的键,值为分组后的元素组成的数组
|
||||
*/
|
||||
public groupBy<TResult>(
|
||||
grouper: (key: T) => string | number,
|
||||
mapper: (element: T) => TResult = val => (val as any) as TResult
|
||||
): { [key: string]: TResult[] } {
|
||||
const initialValue: { [key: string]: TResult[] } = {};
|
||||
return this.aggregate((ac, v: any) => {
|
||||
const key = grouper(v);
|
||||
const existingGroup = ac[key];
|
||||
const mappedValue = mapper(v);
|
||||
existingGroup
|
||||
? existingGroup.push(mappedValue)
|
||||
: (ac[key] = [mappedValue]);
|
||||
return ac;
|
||||
}, initialValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将两个数组进行联接和分组操作
|
||||
* @param list 要联接的数组
|
||||
* @param key1 用于从第一个数组中选择分组键的函数
|
||||
* @param key2 用于从第二个数组中选择分组键的函数
|
||||
* @param result 用于将分组结果映射到输出元素的函数
|
||||
* @returns 经过联接和分组后的新数组
|
||||
*/
|
||||
public groupJoin<U, R>(
|
||||
list: List<U>,
|
||||
key1: (k: T) => any,
|
||||
key2: (k: U) => any,
|
||||
result: (first: T, second: List<U>) => R
|
||||
): List<R> {
|
||||
// 使用 select() 方法对第一个数组中的每个元素进行分组操作
|
||||
return this.select(x =>
|
||||
// 调用 result 函数将分组结果映射到输出元素
|
||||
result(
|
||||
x,
|
||||
// 使用 where() 方法从第二个数组中选择符合条件的元素,然后使用 List 对象进行包装
|
||||
list.where(z => key1(x) === key2(z))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回当前列表中指定元素的索引
|
||||
* @param element 要查找的元素
|
||||
* @returns {number} 元素在列表中的索引值,如果不存在,则返回 -1
|
||||
*/
|
||||
public indexOf(element: T): number {
|
||||
// 调用 indexOf 方法,查找元素在列表中的索引值,如果不存在,则返回 -1
|
||||
return this._elements.indexOf(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在数组的指定位置插入一个元素
|
||||
* @param index 要插入元素的位置
|
||||
* @param element 要插入的元素
|
||||
* @throws 如果索引超出了数组的范围,则抛出异常
|
||||
*/
|
||||
public insert(index: number, element: T): void {
|
||||
// 如果索引小于 0 或大于数组长度,则抛出异常
|
||||
if (index < 0 || index > this._elements.length) {
|
||||
throw new Error('Index is out of range.');
|
||||
}
|
||||
|
||||
// 使用 splice() 方法在指定位置插入元素
|
||||
this._elements.splice(index, 0, element);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前列表和另一个列表的交集
|
||||
* @param source 另一个列表
|
||||
* @returns {List<T>} 一个包含两个列表中相同元素的新列表对象
|
||||
*/
|
||||
public intersect(source: List<T>): List<T> {
|
||||
// 调用 where 方法,传入一个谓词函数,返回一个包含两个列表中相同元素的新列表对象
|
||||
return this.where(x => source.contains(x));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前列表和另一个列表中的元素进行联接
|
||||
* @param list 另一个列表
|
||||
* @param key1 当前列表的键选择器函数
|
||||
* @param key2 另一个列表的键选择器函数
|
||||
* @param result 结果选择器函数
|
||||
* @returns {List<R>} 一个包含联接后元素的新列表对象
|
||||
*/
|
||||
public join<U, R>(
|
||||
list: List<U>,
|
||||
key1: (key: T) => any,
|
||||
key2: (key: U) => any,
|
||||
result: (first: T, second: U) => R
|
||||
): List<R> {
|
||||
// 对当前列表中的每个元素调用 selectMany 方法,并传入一个返回值为列表的函数,最终返回一个新的列表对象
|
||||
return this.selectMany(x =>
|
||||
// 调用 list.where 方法,传入一个谓词函数,返回一个包含与当前元素匹配的元素的新列表对象
|
||||
list.where(y => key2(y) === key1(x)).select(z => result(x, z))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数组的最后一个元素或满足条件的最后一个元素
|
||||
* @param predicate 可选参数,用于筛选元素的函数
|
||||
* @returns 数组的最后一个元素或满足条件的最后一个元素
|
||||
* @throws 如果数组为空,则抛出异常
|
||||
*/
|
||||
public last(predicate?: PredicateType<T>): T {
|
||||
// 如果数组不为空
|
||||
if (this.count()) {
|
||||
// 如果提供了 predicate 函数,则使用 where() 方法进行筛选,并递归调用 last() 方法
|
||||
if (predicate) {
|
||||
return this.where(predicate).last();
|
||||
} else {
|
||||
// 否则,直接返回数组的最后一个元素
|
||||
return this._elements[this.count() - 1];
|
||||
}
|
||||
} else {
|
||||
// 如果数组为空,则抛出异常
|
||||
throw Error('InvalidOperationException: The source sequence is empty.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数组的最后一个元素或满足条件的最后一个元素,如果数组为空或没有满足条件的元素,则返回默认值 undefined
|
||||
* @param predicate 可选参数,用于筛选元素的函数
|
||||
* @returns 数组的最后一个元素或满足条件的最后一个元素,如果数组为空或没有满足条件的元素,则返回默认值 undefined
|
||||
*/
|
||||
public lastOrDefault(predicate?: PredicateType<T>): T {
|
||||
// 如果数组中存在满足条件的元素,则返回最后一个满足条件的元素;否则,返回 undefined
|
||||
return this.count(predicate) ? this.last(predicate) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数组中的最大值,也可以通过 selector 函数对数组元素进行转换后再求最大值
|
||||
* @param selector 可选参数,用于对数组元素进行转换的函数
|
||||
* @returns 数组中的最大值,或者通过 selector 函数对数组元素进行转换后求得的最大值
|
||||
*/
|
||||
public max(selector?: (value: T, index: number, array: T[]) => number): number {
|
||||
// 定义一个默认的转换函数 id,用于当 selector 参数未指定时使用
|
||||
const id = x => x;
|
||||
// 使用 map() 方法对数组元素进行转换,并使用 Math.max() 方法求得最大值
|
||||
return Math.max(...this._elements.map(selector || id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数组中的最小值,也可以通过 selector 函数对数组元素进行转换后再求最小值
|
||||
* @param selector 可选参数,用于对数组元素进行转换的函数
|
||||
* @returns 数组中的最小值,或者通过 selector 函数对数组元素进行转换后求得的最小值
|
||||
*/
|
||||
public min(selector?: (value: T, index: number, array: T[]) => number): number {
|
||||
// 定义一个默认的转换函数 id,用于当 selector 参数未指定时使用
|
||||
const id = x => x;
|
||||
// 使用 map() 方法对数组元素进行转换,并使用 Math.min() 方法求得最小值
|
||||
return Math.min(...this._elements.map(selector || id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的类型,筛选数组中的元素并返回一个新的数组
|
||||
* @param type 指定的类型
|
||||
* @returns 新的数组,其中包含了数组中所有指定类型的元素
|
||||
*/
|
||||
public ofType<U>(type: any): List<U> {
|
||||
let typeName: string;
|
||||
// 使用 switch 语句根据指定类型设置 typeName 变量
|
||||
switch (type) {
|
||||
case Number:
|
||||
typeName = typeof 0;
|
||||
break;
|
||||
case String:
|
||||
typeName = typeof '';
|
||||
break;
|
||||
case Boolean:
|
||||
typeName = typeof true;
|
||||
break;
|
||||
case Function:
|
||||
typeName = typeof function () {
|
||||
}; // 空函数,不做任何操作
|
||||
break;
|
||||
default:
|
||||
typeName = null;
|
||||
break;
|
||||
}
|
||||
// 如果 typeName 为 null,则使用 "instanceof" 运算符检查类型;否则,使用 typeof 运算符检查类型
|
||||
return typeName === null
|
||||
? this.where(x => x instanceof type).cast<U>()
|
||||
: this.where(x => typeof x === typeName).cast<U>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据键按升序对序列中的元素进行排序。
|
||||
*/
|
||||
public orderBy(
|
||||
keySelector: (key: T) => any,
|
||||
comparer = keyComparer(keySelector, false)
|
||||
): List<T> {
|
||||
// tslint:disable-next-line: no-use-before-declare
|
||||
return new OrderedList<T>(this._elements, comparer)
|
||||
}
|
||||
|
||||
/**
|
||||
* 按照指定的键选择器和比较器,对列表元素进行降序排序
|
||||
* @param keySelector 用于选择排序键的函数
|
||||
* @param comparer 可选参数,用于比较元素的函数,如果未指定则使用 keySelector 和降序排序
|
||||
* @returns 排序后的新 List<T> 对象
|
||||
*/
|
||||
public orderByDescending(
|
||||
keySelector: (key: T) => any,
|
||||
comparer = keyComparer(keySelector, true)
|
||||
): List<T> {
|
||||
// 使用 Array.slice() 方法复制数组元素,避免修改原数组
|
||||
const elementsCopy = this._elements.slice();
|
||||
|
||||
// 根据 keySelector 和 comparer 排序元素
|
||||
elementsCopy.sort(comparer);
|
||||
|
||||
// 创建新的 OrderedList<T> 对象并返回
|
||||
return new OrderedList<T>(elementsCopy, comparer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在已经按照一个或多个条件排序的列表上,再按照一个新的条件进行排序
|
||||
* @param keySelector 用于选择新排序键的函数
|
||||
* @returns 排序后的新 List<T> 对象
|
||||
*/
|
||||
public thenBy(keySelector: (key: T) => any): List<T> {
|
||||
// 调用 orderBy 方法,使用 keySelector 函数对列表进行排序,并返回排序后的新列表
|
||||
return this.orderBy(keySelector);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对当前列表中的元素进行降序排序
|
||||
* @param keySelector 键选择器函数,用于对列表中的每个元素进行转换
|
||||
* @returns {List<T>} 一个包含排序后元素的新列表对象
|
||||
*/
|
||||
public thenByDescending(keySelector: (key: T) => any): List<T> {
|
||||
// 调用 orderByDescending 方法,传入键选择器函数,对当前列表中的元素进行降序排序,并返回一个新的列表对象
|
||||
return this.orderByDescending(keySelector);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前列表中删除指定元素
|
||||
* @param element 要删除的元素
|
||||
* @returns {boolean} 如果删除成功,则返回 true,否则返回 false
|
||||
*/
|
||||
public remove(element: T): boolean {
|
||||
// 调用 indexOf 方法,查找元素在列表中的索引值
|
||||
const index = this.indexOf(element);
|
||||
// 如果元素存在,则调用 removeAt 方法将其从列表中删除,并返回 true,否则返回 false
|
||||
return index !== -1 ? (this.removeAt(index), true) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前列表中删除满足指定条件的所有元素,并返回一个新的列表对象
|
||||
* @param predicate 谓词函数,用于对列表中的每个元素进行评估
|
||||
* @returns {List<T>} 一个包含不满足条件的元素的新列表对象
|
||||
*/
|
||||
public removeAll(predicate: PredicateType<T>): List<T> {
|
||||
// 调用 negate 函数对谓词函数进行取反,然后使用 where 方法筛选出不满足条件的元素
|
||||
const elements = this.where(negate(predicate as any)).toArray();
|
||||
// 创建一个新的列表对象,包含不满足条件的元素,并返回该对象
|
||||
return new List<T>(elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前列表中删除指定索引位置的元素
|
||||
* @param index 要删除的元素在列表中的索引值
|
||||
*/
|
||||
public removeAt(index: number): void {
|
||||
// 使用 splice 方法,传入要删除的元素在列表中的索引值和要删除的元素个数,以从列表中删除指定索引位置的元素
|
||||
this._elements.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 反转当前列表中的元素顺序
|
||||
* @returns {List<T>} 一个包含反转后元素的新列表对象
|
||||
*/
|
||||
public reverse(): List<T> {
|
||||
// 调用 reverse 方法,反转当前列表中的元素顺序,并使用这些反转后的元素创建一个新的列表对象
|
||||
return new List<T>(this._elements.reverse());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对数组中的每个元素进行转换,生成新的数组
|
||||
* @param selector 将数组中的每个元素转换成另外的值
|
||||
* @returns 新的 List 对象,包含转换后的元素
|
||||
*/
|
||||
public select<TOut>(
|
||||
selector: (element: T, index: number) => TOut
|
||||
): List<TOut> {
|
||||
// 使用 map() 方法对数组中的每个元素进行转换,生成新的数组
|
||||
const transformedArray = this._elements.map(selector);
|
||||
// 将新数组封装成 List 对象并返回
|
||||
return new List<TOut>(transformedArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对数组中的每个元素进行转换,并将多个新数组合并成一个数组
|
||||
* @param selector 将数组中的每个元素转换成新的数组
|
||||
* @returns 合并后的新数组
|
||||
*/
|
||||
public selectMany<TOut extends List<any>>(
|
||||
selector: (element: T, index: number) => TOut
|
||||
): TOut {
|
||||
// 使用 aggregate() 方法对数组中的每个元素进行转换,并将多个新数组合并成一个数组
|
||||
return this.aggregate(
|
||||
(accumulator, _, index) => {
|
||||
// 获取当前元素对应的新数组
|
||||
const selectedArray = this.select(selector).elementAt(index);
|
||||
// 将新数组中的所有元素添加到累加器中
|
||||
return accumulator.addRange(selectedArray.toArray());
|
||||
},
|
||||
new List<TOut>()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较当前列表和指定列表是否相等
|
||||
* @param list 要比较的列表对象
|
||||
* @returns {boolean} 如果列表相等,则返回 true,否则返回 false
|
||||
*/
|
||||
public sequenceEqual(list: List<T>): boolean {
|
||||
// 调用 all 方法,传入一个谓词函数,用于对当前列表中的每个元素进行评估
|
||||
// 在谓词函数中调用 contains 方法,传入当前元素和指定列表对象,以检查当前元素是否存在于指定列表中
|
||||
// 如果当前列表中的所有元素都存在于指定列表中,则返回 true,否则返回 false
|
||||
return this.all(e => list.contains(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前列表中获取一个满足指定条件的唯一元素
|
||||
* @param predicate 谓词函数,用于对列表中的每个元素进行评估
|
||||
* @returns {T} 列表中唯一满足指定条件的元素
|
||||
* @throws {Error} 如果列表中不恰好包含一个满足指定条件的元素,则抛出异常
|
||||
*/
|
||||
public single(predicate?: PredicateType<T>): T {
|
||||
// 调用 count 方法,传入谓词函数,以获取满足指定条件的元素个数
|
||||
const count = this.count(predicate);
|
||||
// 如果元素个数不等于 1,则抛出异常
|
||||
if (count !== 1) {
|
||||
throw new Error('The collection does not contain exactly one element.');
|
||||
}
|
||||
// 调用 first 方法,传入谓词函数,以获取唯一元素并返回
|
||||
return this.first(predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从当前列表中获取一个满足指定条件的唯一元素,如果没有元素满足条件,则返回默认值 undefined
|
||||
* @param predicate 谓词函数,用于对列表中的每个元素进行评估
|
||||
* @returns {T} 列表中唯一满足指定条件的元素,如果没有元素满足条件,则返回默认值 undefined
|
||||
*/
|
||||
public singleOrDefault(predicate?: PredicateType<T>): T {
|
||||
// 如果元素个数为真值,则调用 single 方法,传入谓词函数,以获取唯一元素并返回
|
||||
// 否则,返回默认值 undefined
|
||||
return this.count(predicate) ? this.single(predicate) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 List 的开头跳过指定数量的元素并返回剩余元素的新 List。
|
||||
* 如果指定数量大于 List 中的元素数,则返回一个空的 List。
|
||||
* @param amount 要跳过的元素数量
|
||||
* @returns 新 List
|
||||
*/
|
||||
public skip(amount: number): List<T> {
|
||||
return new List<T>(this._elements.slice(Math.max(0, amount)))
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回由源 List 中除了最后指定数量的元素之外的所有元素组成的 List。
|
||||
* @param amount 要跳过的元素数。
|
||||
* @returns 由源 List 中除了最后指定数量的元素之外的所有元素组成的 List。
|
||||
*/
|
||||
public skipLast(amount: number): List<T> {
|
||||
return new List<T>(this._elements.slice(0, -Math.max(0, amount)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 List 的开头开始,跳过符合指定谓词的元素,并返回剩余元素。
|
||||
* @param predicate 用于测试每个元素是否应跳过的函数。
|
||||
* @returns 一个新 List,包含源 List 中从跳过元素之后到末尾的元素。
|
||||
*/
|
||||
public skipWhile(predicate: PredicateType<T>): List<T> {
|
||||
// aggregate() 函数接收一个函数作为参数,将该函数应用于 List 的每个元素,并在每次应用后返回一个累加器的值。
|
||||
// 此处使用 aggregate() 函数来计算从 List 的开头开始符合指定谓词的元素个数。
|
||||
return this.skip(
|
||||
this.aggregate(ac => (predicate(this.elementAt(ac)) ? ++ac : ac), 0)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算数组中所有元素的和
|
||||
* @param transform 可选参数,用于将数组中的每个元素转换成另外的值进行计算
|
||||
* @returns 数组的和
|
||||
*/
|
||||
public sum(
|
||||
transform?: (value?: T, index?: number, list?: T[]) => number
|
||||
): number {
|
||||
// 如果提供了 transform 函数,则使用 select() 方法将每个元素转换成新的值,并调用 sum() 方法计算新数组的和
|
||||
if (transform) {
|
||||
return this.select(transform).sum();
|
||||
}
|
||||
// 如果没有提供 transform 函数,则使用 aggregate() 方法计算数组的和
|
||||
// 这里使用加号 + 将元素转换为数值型
|
||||
return this.aggregate((ac, v) => (ac += +v), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 List 的开头返回指定数量的连续元素。
|
||||
* @param amount 要返回的元素数量
|
||||
* @returns 一个新的 List,其中包含原始 List 中开头的指定数量的元素
|
||||
*/
|
||||
public take(amount: number): List<T> {
|
||||
// 使用 slice() 函数截取原始 List 中的指定数量的元素
|
||||
return new List<T>(this._elements.slice(0, Math.max(0, amount)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从列表末尾开始获取指定数量的元素,返回一个新的 List 对象。
|
||||
* @param amount 需要获取的元素数量。
|
||||
* @returns 一个新的 List 对象,包含从末尾开始的指定数量的元素。
|
||||
*/
|
||||
public takeLast(amount: number): List<T> {
|
||||
// Math.max(0, amount) 确保 amount 大于 0,如果 amount 是负数,则返回 0。
|
||||
// slice() 方法从数组的指定位置开始提取元素,返回一个新的数组。
|
||||
// 此处使用 slice() 方法返回 List 中末尾指定数量的元素。
|
||||
return new List<T>(this._elements.slice(-Math.max(0, amount)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 List 的开头开始取出符合指定谓词的元素,直到不符合为止,返回这些元素组成的 List。
|
||||
* @param predicate 用于测试每个元素是否符合条件的函数。
|
||||
* @returns 符合条件的元素组成的 List。
|
||||
*/
|
||||
public takeWhile(predicate: PredicateType<T>): List<T> {
|
||||
// aggregate() 函数接收一个函数作为参数,将该函数应用于 List 的每个元素,并在每次应用后返回一个累加器的值。
|
||||
// 此处使用 aggregate() 函数来计算从 List 的开头开始符合指定谓词的元素个数。
|
||||
return this.take(
|
||||
this.aggregate(ac => (predicate(this.elementAt(ac)) ? ++ac : ac), 0)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制列表中的元素到一个新数组。
|
||||
*/
|
||||
public toArray(): T[] {
|
||||
return this._elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数组转换为字典,根据指定的键和值对元素进行分组并返回一个新的字典
|
||||
* @param key 指定的键,用于分组
|
||||
* @param value 可选参数,指定的值,用于分组后的元素的值;如果未指定,则默认使用原始元素
|
||||
* @returns 分组后的元素组成的新的字典
|
||||
*/
|
||||
public toDictionary<TKey, TValue = T>(
|
||||
key: (key: T) => TKey,
|
||||
value?: (value: T) => TValue
|
||||
): List<{ Key: TKey; Value: T | TValue }> {
|
||||
return this.aggregate((dicc, v, i) => {
|
||||
// 使用 select() 方法获取元素的键和值,并将其添加到字典中
|
||||
dicc[
|
||||
this.select(key)
|
||||
.elementAt(i)
|
||||
.toString()
|
||||
] = value ? this.select(value).elementAt(i) : v;
|
||||
// 将键和值添加到结果列表中
|
||||
dicc.add({
|
||||
Key: this.select(key).elementAt(i),
|
||||
Value: value ? this.select(value).elementAt(i) : v
|
||||
});
|
||||
return dicc;
|
||||
}, new List<{ Key: TKey; Value: T | TValue }>());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数组转换为一个 Set 对象
|
||||
* @returns Set 对象,其中包含了数组中的所有元素
|
||||
*/
|
||||
public toSet() {
|
||||
let result = new Set();
|
||||
for (let x of this._elements)
|
||||
result.add(x);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将数组转换为一个查找表,根据指定的键对元素进行分组并返回一个包含键值对的对象
|
||||
* @param keySelector 指定的键,用于分组
|
||||
* @param elementSelector 可选参数,指定的值,用于分组后的元素的值;如果未指定,则默认使用原始元素
|
||||
* @returns 包含键值对的对象,其中键为分组后的键,值为分组后的元素组成的数组
|
||||
*/
|
||||
public toLookup<TResult>(
|
||||
keySelector: (key: T) => string | number,
|
||||
elementSelector: (element: T) => TResult
|
||||
): { [key: string]: TResult[] } {
|
||||
return this.groupBy(keySelector, elementSelector);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的条件,筛选数组中的元素并返回一个新的数组
|
||||
* @param predicate 指定的条件
|
||||
* @returns 新的数组,其中包含了数组中所有满足条件的元素
|
||||
*/
|
||||
public where(predicate: PredicateType<T>): List<T> {
|
||||
return new List<T>(this._elements.filter(predicate));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据指定的函数将两个数组合并成一个新的数组
|
||||
* @param list 要合并的数组
|
||||
* @param result 指定的函数,用于将两个元素合并为一个
|
||||
* @returns 合并后的新数组
|
||||
*/
|
||||
public zip<U, TOut>(
|
||||
list: List<U>,
|
||||
result: (first: T, second: U) => TOut
|
||||
): List<TOut> {
|
||||
if (list.count() < this.count()) {
|
||||
// 如果要合并的数组的长度小于当前数组的长度,就使用要合并的数组的长度进行循环迭代
|
||||
return list.select((x, y) => result(this.elementAt(y), x));
|
||||
} else {
|
||||
// 如果要合并的数组的长度大于或等于当前数组的长度,就使用当前数组的长度进行循环迭代
|
||||
return this.select((x, y) => result(x, list.elementAt(y)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表示已排序的序列。该类的方法是通过使用延迟执行来实现的。
|
||||
* 即时返回值是一个存储执行操作所需的所有信息的对象。
|
||||
* 在通过调用对象的ToDictionary、ToLookup、ToList或ToArray方法枚举对象之前,不会执行由该方法表示的查询
|
||||
*/
|
||||
export class OrderedList<T> extends List<T> {
|
||||
constructor(elements: T[], private _comparer: (a: T, b: T) => number) {
|
||||
super(elements);
|
||||
this._elements.sort(this._comparer); // 对元素数组进行排序
|
||||
}
|
||||
|
||||
/**
|
||||
* 按键按升序对序列中的元素执行后续排序。
|
||||
* @override
|
||||
*/
|
||||
public thenBy(keySelector: (key: T) => any): List<T> {
|
||||
return new OrderedList(
|
||||
this._elements,
|
||||
composeComparers(this._comparer, keyComparer(keySelector, false))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据键值按降序对序列中的元素执行后续排序。
|
||||
* @override
|
||||
*/
|
||||
public thenByDescending(keySelector: (key: T) => any): List<T> {
|
||||
return new OrderedList(
|
||||
this._elements,
|
||||
composeComparers(this._comparer, keyComparer(keySelector, true))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
module es {
|
||||
export interface IListener {
|
||||
caller: object;
|
||||
callback: Function;
|
||||
}
|
||||
|
||||
export interface IObservable {
|
||||
addListener(caller: object, callback: Function);
|
||||
removeListener(caller: object, callback: Function);
|
||||
clearListener();
|
||||
clearListenerWithCaller(caller: object);
|
||||
}
|
||||
|
||||
export class Observable implements IObservable {
|
||||
public constructor() {
|
||||
this._listeners = [];
|
||||
}
|
||||
|
||||
public addListener(caller: object, callback: Function) {
|
||||
if (
|
||||
this._listeners.findIndex(
|
||||
listener =>
|
||||
listener.callback === callback && listener.caller === caller
|
||||
) === -1
|
||||
) {
|
||||
this._listeners.push({ caller, callback });
|
||||
}
|
||||
}
|
||||
|
||||
public removeListener(caller: object, callback: Function) {
|
||||
const index = this._listeners.findIndex(
|
||||
listener => listener.callback === callback && listener.caller === caller
|
||||
);
|
||||
if (index >= 0) {
|
||||
this._listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public clearListener() {
|
||||
this._listeners = [];
|
||||
}
|
||||
|
||||
public clearListenerWithCaller(caller: object) {
|
||||
for (let i = this._listeners.length - 1; i >= 0; i--) {
|
||||
const listener = this._listeners[i];
|
||||
if (listener.caller === caller) {
|
||||
this._listeners.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public notify(...args) {
|
||||
for (let i = this._listeners.length - 1; i >= 0; i--) {
|
||||
const listener = this._listeners[i];
|
||||
if (listener.caller) {
|
||||
listener.callback.call(listener.caller, ...args);
|
||||
} else {
|
||||
listener.callback(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _listeners: IListener[];
|
||||
}
|
||||
|
||||
export class ObservableT<T> extends Observable {
|
||||
public addListener(caller: object, callback: (arg: T) => void) {
|
||||
super.addListener(caller, callback);
|
||||
}
|
||||
|
||||
public removeListener(caller: object, callback: (arg: T) => void) {
|
||||
super.removeListener(caller, callback);
|
||||
}
|
||||
|
||||
public notify(arg: T) {
|
||||
super.notify(arg);
|
||||
}
|
||||
}
|
||||
|
||||
export class ObservableTT<T, R> extends Observable {
|
||||
public addListener(caller: object, callback: (arg1: T, arg2: R) => void) {
|
||||
super.addListener(caller, callback);
|
||||
}
|
||||
|
||||
public removeListener(caller: object, callback: (arg: T, arg2: R) => void) {
|
||||
super.removeListener(caller, callback);
|
||||
}
|
||||
|
||||
public notify(arg1: T, arg2: R) {
|
||||
super.notify(arg1, arg2);
|
||||
}
|
||||
}
|
||||
|
||||
export class Command implements IObservable {
|
||||
public constructor(caller: object, action: Function) {
|
||||
this.bindAction(caller, action);
|
||||
this._onExec = new Observable();
|
||||
}
|
||||
|
||||
public bindAction(caller: object, action: Function) {
|
||||
this._caller = caller;
|
||||
this._action = action;
|
||||
}
|
||||
|
||||
public dispatch(...args: any[]) {
|
||||
if (this._action) {
|
||||
if (this._caller) {
|
||||
this._action.call(this._caller, ...args);
|
||||
} else {
|
||||
this._action(...args);
|
||||
}
|
||||
this._onExec.notify();
|
||||
} else {
|
||||
console.warn('command not bind with an action');
|
||||
}
|
||||
}
|
||||
|
||||
public addListener(caller: object, callback: Function) {
|
||||
this._onExec.addListener(caller, callback);
|
||||
}
|
||||
|
||||
public removeListener(caller: object, callback: Function) {
|
||||
this._onExec.removeListener(caller, callback);
|
||||
}
|
||||
|
||||
public clearListener() {
|
||||
this._onExec.clearListener();
|
||||
}
|
||||
|
||||
public clearListenerWithCaller(caller: object) {
|
||||
this._onExec.clearListenerWithCaller(caller);
|
||||
}
|
||||
|
||||
private _onExec: Observable;
|
||||
private _caller: object;
|
||||
private _action: Function;
|
||||
}
|
||||
|
||||
export class ValueChangeCommand<T> implements IObservable {
|
||||
public constructor(value: T) {
|
||||
this._onValueChange = new Observable();
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
public get onValueChange() {
|
||||
return this._onValueChange;
|
||||
}
|
||||
|
||||
public get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public set value(newValue: T) {
|
||||
this._value = newValue;
|
||||
}
|
||||
|
||||
public dispatch(value: T) {
|
||||
if (value !== this._value) {
|
||||
const oldValue = this._value;
|
||||
this._value = value;
|
||||
this._onValueChange.notify(this._value, oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
public addListener(caller: object, callback: Function) {
|
||||
this._onValueChange.addListener(caller, callback);
|
||||
}
|
||||
|
||||
public removeListener(caller: object, callback: Function) {
|
||||
this._onValueChange.removeListener(caller, callback);
|
||||
}
|
||||
|
||||
public clearListener() {
|
||||
this._onValueChange.clearListener();
|
||||
}
|
||||
|
||||
public clearListenerWithCaller(caller: object) {
|
||||
this._onValueChange.clearListenerWithCaller(caller);
|
||||
}
|
||||
|
||||
private _onValueChange: Observable;
|
||||
private _value: T;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
module es {
|
||||
export class Out<T> {
|
||||
public value: T;
|
||||
|
||||
constructor(value: T = null) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
403
source/src/Utils/PerformanceMonitor.ts
Normal file
403
source/src/Utils/PerformanceMonitor.ts
Normal file
@@ -0,0 +1,403 @@
|
||||
/**
|
||||
* 性能监控数据
|
||||
*/
|
||||
export interface PerformanceData {
|
||||
/** 系统名称 */
|
||||
name: string;
|
||||
/** 执行时间(毫秒) */
|
||||
executionTime: number;
|
||||
/** 处理的实体数量 */
|
||||
entityCount: number;
|
||||
/** 平均每个实体的处理时间 */
|
||||
averageTimePerEntity: number;
|
||||
/** 最后更新时间戳 */
|
||||
lastUpdateTime: number;
|
||||
/** 内存使用量(字节) */
|
||||
memoryUsage?: number;
|
||||
/** CPU使用率(百分比) */
|
||||
cpuUsage?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能统计信息
|
||||
*/
|
||||
export interface PerformanceStats {
|
||||
/** 总执行时间 */
|
||||
totalTime: number;
|
||||
/** 平均执行时间 */
|
||||
averageTime: number;
|
||||
/** 最小执行时间 */
|
||||
minTime: number;
|
||||
/** 最大执行时间 */
|
||||
maxTime: number;
|
||||
/** 执行次数 */
|
||||
executionCount: number;
|
||||
/** 最近的执行时间列表 */
|
||||
recentTimes: number[];
|
||||
/** 标准差 */
|
||||
standardDeviation: number;
|
||||
/** 95百分位数 */
|
||||
percentile95: number;
|
||||
/** 99百分位数 */
|
||||
percentile99: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能警告类型
|
||||
*/
|
||||
export enum PerformanceWarningType {
|
||||
HIGH_EXECUTION_TIME = 'high_execution_time',
|
||||
HIGH_MEMORY_USAGE = 'high_memory_usage',
|
||||
HIGH_CPU_USAGE = 'high_cpu_usage',
|
||||
FREQUENT_GC = 'frequent_gc',
|
||||
LOW_FPS = 'low_fps',
|
||||
HIGH_ENTITY_COUNT = 'high_entity_count'
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能警告
|
||||
*/
|
||||
export interface PerformanceWarning {
|
||||
type: PerformanceWarningType;
|
||||
systemName: string;
|
||||
message: string;
|
||||
severity: 'low' | 'medium' | 'high' | 'critical';
|
||||
timestamp: number;
|
||||
value: number;
|
||||
threshold: number;
|
||||
suggestion?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能阈值配置
|
||||
*/
|
||||
export interface PerformanceThresholds {
|
||||
/** 执行时间阈值(毫秒) */
|
||||
executionTime: {
|
||||
warning: number;
|
||||
critical: number;
|
||||
};
|
||||
/** 内存使用阈值(MB) */
|
||||
memoryUsage: {
|
||||
warning: number;
|
||||
critical: number;
|
||||
};
|
||||
/** CPU使用率阈值(百分比) */
|
||||
cpuUsage: {
|
||||
warning: number;
|
||||
critical: number;
|
||||
};
|
||||
/** FPS阈值 */
|
||||
fps: {
|
||||
warning: number;
|
||||
critical: number;
|
||||
};
|
||||
/** 实体数量阈值 */
|
||||
entityCount: {
|
||||
warning: number;
|
||||
critical: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 高性能监控器
|
||||
* 用于监控ECS系统的性能表现,提供详细的分析和优化建议
|
||||
*/
|
||||
export class PerformanceMonitor {
|
||||
private static _instance: PerformanceMonitor;
|
||||
|
||||
private _systemData = new Map<string, PerformanceData>();
|
||||
private _systemStats = new Map<string, PerformanceStats>();
|
||||
private _warnings: PerformanceWarning[] = [];
|
||||
private _isEnabled = false;
|
||||
private _maxRecentSamples = 60; // 保留最近60帧的数据
|
||||
private _maxWarnings = 100; // 最大警告数量
|
||||
|
||||
// 性能阈值配置
|
||||
private _thresholds: PerformanceThresholds = {
|
||||
executionTime: { warning: 16.67, critical: 33.33 }, // 60fps和30fps对应的帧时间
|
||||
memoryUsage: { warning: 100, critical: 200 }, // MB
|
||||
cpuUsage: { warning: 70, critical: 90 }, // 百分比
|
||||
fps: { warning: 45, critical: 30 },
|
||||
entityCount: { warning: 1000, critical: 5000 }
|
||||
};
|
||||
|
||||
// FPS监控
|
||||
private _fpsHistory: number[] = [];
|
||||
private _lastFrameTime = 0;
|
||||
private _frameCount = 0;
|
||||
private _fpsUpdateInterval = 1000; // 1秒更新一次FPS
|
||||
private _lastFpsUpdate = 0;
|
||||
private _currentFps = 60;
|
||||
|
||||
// 内存监控
|
||||
private _memoryCheckInterval = 5000; // 5秒检查一次内存
|
||||
private _lastMemoryCheck = 0;
|
||||
private _memoryHistory: number[] = [];
|
||||
|
||||
// GC监控
|
||||
private _gcCount = 0;
|
||||
private _lastGcCheck = 0;
|
||||
private _gcCheckInterval = 1000;
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*/
|
||||
public static get instance(): PerformanceMonitor {
|
||||
if (!PerformanceMonitor._instance) {
|
||||
PerformanceMonitor._instance = new PerformanceMonitor();
|
||||
}
|
||||
return PerformanceMonitor._instance;
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* 启用性能监控
|
||||
*/
|
||||
public enable(): void {
|
||||
this._isEnabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用性能监控
|
||||
*/
|
||||
public disable(): void {
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否启用了性能监控
|
||||
*/
|
||||
public get isEnabled(): boolean {
|
||||
return this._isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始监控系统性能
|
||||
* @param systemName 系统名称
|
||||
* @returns 开始时间戳
|
||||
*/
|
||||
public startMonitoring(systemName: string): number {
|
||||
if (!this._isEnabled) {
|
||||
return 0;
|
||||
}
|
||||
return performance.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束监控并记录性能数据
|
||||
* @param systemName 系统名称
|
||||
* @param startTime 开始时间戳
|
||||
* @param entityCount 处理的实体数量
|
||||
*/
|
||||
public endMonitoring(systemName: string, startTime: number, entityCount: number = 0): void {
|
||||
if (!this._isEnabled || startTime === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const executionTime = endTime - startTime;
|
||||
const averageTimePerEntity = entityCount > 0 ? executionTime / entityCount : 0;
|
||||
|
||||
// 更新当前性能数据
|
||||
const data: PerformanceData = {
|
||||
name: systemName,
|
||||
executionTime,
|
||||
entityCount,
|
||||
averageTimePerEntity,
|
||||
lastUpdateTime: endTime
|
||||
};
|
||||
|
||||
this._systemData.set(systemName, data);
|
||||
|
||||
// 更新统计信息
|
||||
this.updateStats(systemName, executionTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统统计信息
|
||||
* @param systemName 系统名称
|
||||
* @param executionTime 执行时间
|
||||
*/
|
||||
private updateStats(systemName: string, executionTime: number): void {
|
||||
let stats = this._systemStats.get(systemName);
|
||||
|
||||
if (!stats) {
|
||||
stats = {
|
||||
totalTime: 0,
|
||||
averageTime: 0,
|
||||
minTime: Number.MAX_VALUE,
|
||||
maxTime: 0,
|
||||
executionCount: 0,
|
||||
recentTimes: [],
|
||||
standardDeviation: 0,
|
||||
percentile95: 0,
|
||||
percentile99: 0
|
||||
};
|
||||
this._systemStats.set(systemName, stats);
|
||||
}
|
||||
|
||||
// 更新基本统计
|
||||
stats.totalTime += executionTime;
|
||||
stats.executionCount++;
|
||||
stats.averageTime = stats.totalTime / stats.executionCount;
|
||||
stats.minTime = Math.min(stats.minTime, executionTime);
|
||||
stats.maxTime = Math.max(stats.maxTime, executionTime);
|
||||
|
||||
// 更新最近时间列表
|
||||
stats.recentTimes.push(executionTime);
|
||||
if (stats.recentTimes.length > this._maxRecentSamples) {
|
||||
stats.recentTimes.shift();
|
||||
}
|
||||
|
||||
// 计算高级统计信息
|
||||
this.calculateAdvancedStats(stats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算高级统计信息
|
||||
* @param stats 统计信息对象
|
||||
*/
|
||||
private calculateAdvancedStats(stats: PerformanceStats): void {
|
||||
if (stats.recentTimes.length === 0) return;
|
||||
|
||||
// 计算标准差
|
||||
const mean = stats.recentTimes.reduce((a, b) => a + b, 0) / stats.recentTimes.length;
|
||||
const variance = stats.recentTimes.reduce((acc, time) => acc + Math.pow(time - mean, 2), 0) / stats.recentTimes.length;
|
||||
stats.standardDeviation = Math.sqrt(variance);
|
||||
|
||||
// 计算百分位数
|
||||
const sortedTimes = [...stats.recentTimes].sort((a, b) => a - b);
|
||||
const len = sortedTimes.length;
|
||||
|
||||
stats.percentile95 = sortedTimes[Math.floor(len * 0.95)] || 0;
|
||||
stats.percentile99 = sortedTimes[Math.floor(len * 0.99)] || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的当前性能数据
|
||||
* @param systemName 系统名称
|
||||
* @returns 性能数据或undefined
|
||||
*/
|
||||
public getSystemData(systemName: string): PerformanceData | undefined {
|
||||
return this._systemData.get(systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统的统计信息
|
||||
* @param systemName 系统名称
|
||||
* @returns 统计信息或undefined
|
||||
*/
|
||||
public getSystemStats(systemName: string): PerformanceStats | undefined {
|
||||
return this._systemStats.get(systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有系统的性能数据
|
||||
* @returns 所有系统的性能数据
|
||||
*/
|
||||
public getAllSystemData(): Map<string, PerformanceData> {
|
||||
return new Map(this._systemData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有系统的统计信息
|
||||
* @returns 所有系统的统计信息
|
||||
*/
|
||||
public getAllSystemStats(): Map<string, PerformanceStats> {
|
||||
return new Map(this._systemStats);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性能报告
|
||||
* @returns 格式化的性能报告字符串
|
||||
*/
|
||||
public getPerformanceReport(): string {
|
||||
if (!this._isEnabled) {
|
||||
return "Performance monitoring is disabled.";
|
||||
}
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push("=== ECS Performance Report ===");
|
||||
lines.push("");
|
||||
|
||||
// 按平均执行时间排序
|
||||
const sortedSystems = Array.from(this._systemStats.entries())
|
||||
.sort((a, b) => b[1].averageTime - a[1].averageTime);
|
||||
|
||||
for (const [systemName, stats] of sortedSystems) {
|
||||
const data = this._systemData.get(systemName);
|
||||
|
||||
lines.push(`System: ${systemName}`);
|
||||
lines.push(` Current: ${data?.executionTime.toFixed(2)}ms (${data?.entityCount} entities)`);
|
||||
lines.push(` Average: ${stats.averageTime.toFixed(2)}ms`);
|
||||
lines.push(` Min/Max: ${stats.minTime.toFixed(2)}ms / ${stats.maxTime.toFixed(2)}ms`);
|
||||
lines.push(` Total: ${stats.totalTime.toFixed(2)}ms (${stats.executionCount} calls)`);
|
||||
|
||||
if (data?.averageTimePerEntity && data.averageTimePerEntity > 0) {
|
||||
lines.push(` Per Entity: ${data.averageTimePerEntity.toFixed(4)}ms`);
|
||||
}
|
||||
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
// 总体统计
|
||||
const totalCurrentTime = Array.from(this._systemData.values())
|
||||
.reduce((sum, data) => sum + data.executionTime, 0);
|
||||
|
||||
lines.push(`Total Frame Time: ${totalCurrentTime.toFixed(2)}ms`);
|
||||
lines.push(`Systems Count: ${this._systemData.size}`);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置所有性能数据
|
||||
*/
|
||||
public reset(): void {
|
||||
this._systemData.clear();
|
||||
this._systemStats.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置指定系统的性能数据
|
||||
* @param systemName 系统名称
|
||||
*/
|
||||
public resetSystem(systemName: string): void {
|
||||
this._systemData.delete(systemName);
|
||||
this._systemStats.delete(systemName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性能警告
|
||||
* @param thresholdMs 警告阈值(毫秒)
|
||||
* @returns 超过阈值的系统列表
|
||||
*/
|
||||
public getPerformanceWarnings(thresholdMs: number = 16.67): string[] {
|
||||
const warnings: string[] = [];
|
||||
|
||||
for (const [systemName, data] of this._systemData.entries()) {
|
||||
if (data.executionTime > thresholdMs) {
|
||||
warnings.push(`${systemName}: ${data.executionTime.toFixed(2)}ms (>${thresholdMs}ms)`);
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置最大保留样本数
|
||||
* @param maxSamples 最大样本数
|
||||
*/
|
||||
public setMaxRecentSamples(maxSamples: number): void {
|
||||
this._maxRecentSamples = maxSamples;
|
||||
|
||||
// 裁剪现有数据
|
||||
for (const stats of this._systemStats.values()) {
|
||||
while (stats.recentTimes.length > maxSamples) {
|
||||
stats.recentTimes.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 一段的终点
|
||||
*/
|
||||
export class EndPoint {
|
||||
/** 该部分的位置 */
|
||||
public position: Vector2;
|
||||
/** 如果这个端点是一个段的起始点或终点(每个段只有一个起始点和一个终点) */
|
||||
public begin: boolean;
|
||||
/** 该端点所属的段 */
|
||||
public segment: Segment;
|
||||
/** 端点相对于能见度测试位置的角度 */
|
||||
public angle: number;
|
||||
|
||||
constructor() {
|
||||
this.position = Vector2.zero;
|
||||
this.begin = false;
|
||||
this.segment = null;
|
||||
this.angle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class EndPointComparer implements IComparer<EndPoint> {
|
||||
/**
|
||||
* 按角度对点进行排序的比较功能
|
||||
* @param a
|
||||
* @param b
|
||||
*/
|
||||
public compare(a: EndPoint, b: EndPoint) {
|
||||
// 按角度顺序移动
|
||||
if (a.angle > b.angle)
|
||||
return 1;
|
||||
|
||||
if (a.angle < b.angle)
|
||||
return -1;
|
||||
|
||||
// 但对于纽带,我们希望Begin节点在End节点之前
|
||||
if (!a.begin && b.begin)
|
||||
return 1;
|
||||
|
||||
if (a.begin && !b.begin)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 表示可见性网格中的遮挡线段
|
||||
*/
|
||||
export class Segment {
|
||||
/**
|
||||
* 该部分的第一个终点
|
||||
*/
|
||||
public p1: EndPoint;
|
||||
/**
|
||||
* 该部分的第二个终点
|
||||
*/
|
||||
public p2: EndPoint;
|
||||
|
||||
constructor(){
|
||||
this.p1 = null;
|
||||
this.p2 = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,357 +0,0 @@
|
||||
///<reference path="../Collections/LinkList.ts" />
|
||||
module es {
|
||||
/**
|
||||
* 类,它可以计算出一个网格,表示从给定的一组遮挡物的原点可以看到哪些区域。使用方法如下。
|
||||
*
|
||||
* - 调用 begin
|
||||
* - 添加任何遮挡物
|
||||
* - 调用end来获取可见度多边形。当调用end时,所有的内部存储都会被清空。
|
||||
*/
|
||||
export class VisibilityComputer {
|
||||
/**
|
||||
* 在近似圆的时候要用到的线的总数。只需要一个180度的半球,所以这将是近似该半球的线段数
|
||||
*/
|
||||
public lineCountForCircleApproximation: number = 10;
|
||||
|
||||
public _radius: number = 0;
|
||||
public _origin: Vector2 = Vector2.zero;
|
||||
public _isSpotLight: boolean = false;
|
||||
public _spotStartAngle: number = 0;
|
||||
public _spotEndAngle: number = 0;
|
||||
|
||||
public _endPoints: EndPoint[] = [];
|
||||
public _segments: Segment[] = [];
|
||||
public _radialComparer: EndPointComparer;
|
||||
|
||||
public static _cornerCache: Vector2[] = [];
|
||||
public static _openSegments: LinkedList<Segment> = new LinkedList<Segment>();
|
||||
|
||||
constructor(origin?: Vector2, radius?: number){
|
||||
this._origin = origin;
|
||||
this._radius = radius;
|
||||
this._radialComparer = new EndPointComparer();
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加了一个对撞机作为PolyLight的遮蔽器
|
||||
* @param collider
|
||||
*/
|
||||
public addColliderOccluder(collider: Collider) {
|
||||
// 特殊情况下,BoxColliders没有旋转
|
||||
if (collider instanceof BoxCollider && collider.rotation == 0) {
|
||||
this.addSquareOccluder(collider.bounds);
|
||||
return;
|
||||
}
|
||||
|
||||
if (collider instanceof PolygonCollider) {
|
||||
let poly = collider.shape as Polygon;
|
||||
for (let i = 0; i < poly.points.length; i ++) {
|
||||
let firstIndex = i - 1;
|
||||
if (i == 0)
|
||||
firstIndex += poly.points.length;
|
||||
this.addLineOccluder(Vector2.add(poly.points[firstIndex], poly.position),
|
||||
Vector2.add(poly.points[i], poly.position));
|
||||
}
|
||||
} else if(collider instanceof CircleCollider) {
|
||||
this.addCircleOccluder(collider.absolutePosition, collider.radius);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加了一个圆形的遮挡器
|
||||
* @param position
|
||||
* @param radius
|
||||
*/
|
||||
public addCircleOccluder(position: Vector2, radius: number){
|
||||
let dirToCircle = position.sub(this._origin);
|
||||
let angle = Math.atan2(dirToCircle.y, dirToCircle.x);
|
||||
|
||||
let stepSize = Math.PI / this.lineCountForCircleApproximation;
|
||||
let startAngle = angle + MathHelper.PiOver2;
|
||||
let lastPt = MathHelper.angleToVector(startAngle, radius).addEqual(position);
|
||||
for (let i = 1; i < this.lineCountForCircleApproximation; i ++) {
|
||||
let nextPt = MathHelper.angleToVector(startAngle + i * stepSize, radius).addEqual(position);
|
||||
this.addLineOccluder(lastPt, nextPt);
|
||||
lastPt = nextPt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加一个线型遮挡器
|
||||
* @param p1
|
||||
* @param p2
|
||||
*/
|
||||
public addLineOccluder(p1: Vector2, p2: Vector2) {
|
||||
this.addSegment(p1, p2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加一个方形的遮挡器
|
||||
* @param bounds
|
||||
*/
|
||||
public addSquareOccluder(bounds: Rectangle) {
|
||||
let tr = new Vector2(bounds.right, bounds.top);
|
||||
let bl = new Vector2(bounds.left, bounds.bottom);
|
||||
let br = new Vector2(bounds.right, bounds.bottom);
|
||||
|
||||
this.addSegment(bounds.location, tr);
|
||||
this.addSegment(tr, br);
|
||||
this.addSegment(br, bl);
|
||||
this.addSegment(bl, bounds.location);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个段,第一个点在可视化中显示,但第二个点不显示。
|
||||
* 每个端点都是两个段的一部分,但我们希望只显示一次
|
||||
* @param p1
|
||||
* @param p2
|
||||
*/
|
||||
public addSegment(p1: Vector2, p2: Vector2) {
|
||||
let segment = new Segment();
|
||||
let endPoint1 = new EndPoint();
|
||||
let endPoint2 = new EndPoint();
|
||||
|
||||
endPoint1.position = p1;
|
||||
endPoint1.segment = segment;
|
||||
|
||||
endPoint2.position = p2;
|
||||
endPoint2.segment = segment;
|
||||
|
||||
segment.p1 = endPoint1;
|
||||
segment.p2 = endPoint2;
|
||||
|
||||
this._segments.push(segment);
|
||||
this._endPoints.push(endPoint1);
|
||||
this._endPoints.push(endPoint2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除所有的遮挡物
|
||||
*/
|
||||
public clearOccluders(){
|
||||
this._segments.length = 0;
|
||||
this._endPoints.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为计算机计算当前的聚光做好准备
|
||||
* @param origin
|
||||
* @param radius
|
||||
*/
|
||||
public begin(origin: Vector2, radius: number){
|
||||
this._origin = origin;
|
||||
this._radius = radius;
|
||||
this._isSpotLight = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算可见性多边形,并返回三角形扇形的顶点(减去中心顶点)。返回的数组来自ListPool
|
||||
*/
|
||||
public end(): Vector2[] {
|
||||
let output = ListPool.obtain<Vector2>(Vector2);
|
||||
this.updateSegments();
|
||||
this._endPoints.sort(this._radialComparer.compare);
|
||||
|
||||
let currentAngle = 0;
|
||||
// 在扫描开始时,我们想知道哪些段是活动的。
|
||||
// 最简单的方法是先进行一次段的收集,然后再进行一次段的收集和处理。
|
||||
// 然而,更有效的方法是通过所有的段,找出哪些段与最初的扫描线相交,然后对它们进行分类
|
||||
for (let pass = 0; pass < 2; pass ++) {
|
||||
for (let p of this._endPoints) {
|
||||
let currentOld = VisibilityComputer._openSegments.size() == 0 ? null : VisibilityComputer._openSegments.getHead().element;
|
||||
|
||||
if (p.begin) {
|
||||
// 在列表中的正确位置插入
|
||||
let node = VisibilityComputer._openSegments.getHead();
|
||||
while (node != null && this.isSegmentInFrontOf(p.segment, node.element, this._origin))
|
||||
node = node.next;
|
||||
|
||||
if (node == null)
|
||||
VisibilityComputer._openSegments.push(p.segment);
|
||||
else
|
||||
VisibilityComputer._openSegments.insert(p.segment, VisibilityComputer._openSegments.indexOf(node.element));
|
||||
} else {
|
||||
VisibilityComputer._openSegments.remove(p.segment);
|
||||
}
|
||||
|
||||
let currentNew = null;
|
||||
if (VisibilityComputer._openSegments.size() != 0)
|
||||
currentNew = VisibilityComputer._openSegments.getHead().element;
|
||||
|
||||
if (currentOld != currentNew) {
|
||||
if (pass == 1) {
|
||||
if (!this._isSpotLight || (VisibilityComputer.between(currentAngle, this._spotStartAngle, this._spotEndAngle) &&
|
||||
VisibilityComputer.between(p.angle, this._spotStartAngle, this._spotEndAngle)))
|
||||
this.addTriangle(output, currentAngle, p.angle, currentOld);
|
||||
}
|
||||
|
||||
currentAngle = p.angle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VisibilityComputer._openSegments.clear();
|
||||
this.clearOccluders();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public addTriangle(triangles: Vector2[], angle1: number, angle2: number, segment: Segment) {
|
||||
let p1 = this._origin.clone();
|
||||
let p2 = new Vector2(this._origin.x + Math.cos(angle1), this._origin.y + Math.sin(angle1));
|
||||
let p3 = Vector2.zero;
|
||||
let p4 = Vector2.zero;
|
||||
|
||||
if (segment != null){
|
||||
// 将三角形停在相交线段上
|
||||
p3.x = segment.p1.position.x;
|
||||
p3.y = segment.p1.position.y;
|
||||
p4.x = segment.p2.position.x;
|
||||
p4.y = segment.p2.position.y;
|
||||
} else {
|
||||
p3.x = this._origin.x + Math.cos(angle1) * this._radius * 2;
|
||||
p3.y = this._origin.y + Math.sin(angle1) * this._radius * 2;
|
||||
p4.x = this._origin.x + Math.cos(angle2) * this._radius * 2;
|
||||
p4.y = this._origin.y + Math.sin(angle2) * this._radius * 2;
|
||||
}
|
||||
|
||||
let pBegin = VisibilityComputer.lineLineIntersection(p3, p4, p1, p2);
|
||||
|
||||
p2.x = this._origin.x + Math.cos(angle2);
|
||||
p2.y = this._origin.y + Math.sin(angle2);
|
||||
|
||||
let pEnd = VisibilityComputer.lineLineIntersection(p3, p4, p1, p2);
|
||||
|
||||
triangles.push(pBegin);
|
||||
triangles.push(pEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算直线p1-p2与p3-p4的交点
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param p3
|
||||
* @param p4
|
||||
*/
|
||||
public static lineLineIntersection(p1: Vector2, p2: Vector2, p3: Vector2, p4: Vector2){
|
||||
let s = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x))
|
||||
/ ((p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y));
|
||||
return new Vector2(p1.x + s * (p2.x - p1.x), p1.y + s * (p2.y - p1.y));
|
||||
}
|
||||
|
||||
public static between(value: number, min: number, max: number) {
|
||||
value = (360 + (value % 360)) % 360;
|
||||
min = (3600000 + min) % 360;
|
||||
max = (3600000 + max) % 360;
|
||||
|
||||
if (min < max)
|
||||
return min <= value && value <= max;
|
||||
|
||||
return min <= value || value <= max;
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助函数,用于沿外周线构建分段,以限制光的半径。
|
||||
*/
|
||||
public loadRectangleBoundaries(){
|
||||
this.addSegment(new Vector2(this._origin.x - this._radius, this._origin.y - this._radius),
|
||||
new Vector2(this._origin.x + this._radius, this._origin.y - this._radius));
|
||||
this.addSegment(new Vector2(this._origin.x - this._radius, this._origin.y + this._radius),
|
||||
new Vector2(this._origin.x + this._radius, this._origin.y + this._radius));
|
||||
this.addSegment(new Vector2(this._origin.x - this._radius, this._origin.y - this._radius),
|
||||
new Vector2(this._origin.x - this._radius, this._origin.y + this._radius));
|
||||
this.addSegment(new Vector2(this._origin.x + this._radius, this._origin.y - this._radius),
|
||||
new Vector2(this._origin.x + this._radius, this._origin.y + this._radius));
|
||||
}
|
||||
|
||||
/**
|
||||
* 助手:我们知道a段在b的前面吗?实现不反对称(也就是说,isSegmentInFrontOf(a, b) != (!isSegmentInFrontOf(b, a)))。
|
||||
* 另外要注意的是,在可见性算法中,它只需要在有限的一组情况下工作,我不认为它能处理所有的情况。
|
||||
* 见http://www.redblobgames.com/articles/visibility/segment-sorting.html
|
||||
* @param a
|
||||
* @param b
|
||||
* @param relativeTo
|
||||
*/
|
||||
public isSegmentInFrontOf(a: Segment, b: Segment, relativeTo: Vector2) {
|
||||
// 注意:我们稍微缩短了段,所以在这个算法中,端点的交点(共同)不计入交点。
|
||||
let a1 = VisibilityComputer.isLeftOf(a.p2.position, a.p1.position, VisibilityComputer.interpolate(b.p1.position, b.p2.position, 0.01));
|
||||
let a2 = VisibilityComputer.isLeftOf(a.p2.position, a.p1.position, VisibilityComputer.interpolate(b.p2.position, b.p1.position, 0.01));
|
||||
let a3 = VisibilityComputer.isLeftOf(a.p2.position, a.p1.position, relativeTo);
|
||||
|
||||
let b1 = VisibilityComputer.isLeftOf(b.p2.position, b.p1.position, VisibilityComputer.interpolate(a.p1.position, a.p2.position, 0.01));
|
||||
let b2 = VisibilityComputer.isLeftOf(b.p2.position, b.p1.position, VisibilityComputer.interpolate(a.p2.position, a.p1.position, 0.01));
|
||||
let b3 = VisibilityComputer.isLeftOf(b.p2.position, b.p1.position, relativeTo);
|
||||
|
||||
// 注:考虑A1-A2这条线。如果B1和B2都在一条边上,而relativeTo在另一条边上,那么A就在观看者和B之间。
|
||||
if (b1 == b2 && b2 != b3)
|
||||
return true;
|
||||
if (a1 == a2 && a2 == a3)
|
||||
return true;
|
||||
if (a1 == a2 && a2 != a3)
|
||||
return false;
|
||||
if (b1 == b2 && b2 == b3)
|
||||
return false;
|
||||
|
||||
// 如果A1 !=A2,B1 !=B2,那么我们就有一个交点。
|
||||
// 一个更稳健的实现是在交叉点上分割段,使一部分段在前面,一部分段在后面,但无论如何我们不应该有重叠的碰撞器,所以这不是太重要
|
||||
return false;
|
||||
|
||||
// 注意:以前的实现方式是a.d < b.d.,这比较简单,但当段的大小不一样时,就麻烦了。
|
||||
// 如果你是在一个网格上,而且段的大小相似,那么使用距离将是一个更简单和更快的实现。
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回略微缩短的向量:p * (1 - f) + q * f
|
||||
* @param p
|
||||
* @param q
|
||||
* @param f
|
||||
*/
|
||||
public static interpolate(p: Vector2, q: Vector2, f: number){
|
||||
return new Vector2(p.x * (1 - f) + q.x * f, p.y * (1 - f) + q.y * f);
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回点是否在直线p1-p2的 "左边"。
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param point
|
||||
*/
|
||||
public static isLeftOf(p1: Vector2, p2: Vector2, point: Vector2) {
|
||||
let cross = (p2.x - p1.x) * (point.y - p1.y)
|
||||
- (p2.y - p1.y) * (point.x - p1.x);
|
||||
|
||||
return cross < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理片段,以便我们稍后对它们进行分类
|
||||
*/
|
||||
public updateSegments(){
|
||||
for (let segment of this._segments) {
|
||||
// 注:未来的优化:我们可以记录象限和y/x或x/y比率,并按(象限、比率)排序,而不是调用atan2。
|
||||
// 参见<https://github.com/mikolalysenko/compare-slope>,有一个库可以做到这一点
|
||||
|
||||
segment.p1.angle = Math.atan2(segment.p1.position.y - this._origin.y, segment.p1.position.x - this._origin.x);
|
||||
segment.p2.angle = Math.atan2(segment.p2.position.y - this._origin.y, segment.p2.position.x - this._origin.x);
|
||||
|
||||
// Pi和Pi之间的映射角度
|
||||
let dAngle = segment.p2.angle - segment.p1.angle;
|
||||
if (dAngle <= -Math.PI)
|
||||
dAngle += Math.PI * 2;
|
||||
|
||||
if (dAngle > Math.PI)
|
||||
dAngle -= Math.PI * 2;
|
||||
|
||||
segment.p1.begin = (dAngle > 0);
|
||||
segment.p2.begin = !segment.p1.begin;
|
||||
}
|
||||
|
||||
// 如果我们有一个聚光灯,我们需要存储前两个段的角度。
|
||||
// 这些是光斑的边界,我们将用它们来过滤它们之外的任何顶点。
|
||||
if (this._isSpotLight) {
|
||||
this._spotStartAngle = this._segments[0].p2.angle;
|
||||
this._spotEndAngle = this._segments[1].p2.angle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
565
source/src/Utils/Pool.ts
Normal file
565
source/src/Utils/Pool.ts
Normal file
@@ -0,0 +1,565 @@
|
||||
/**
|
||||
* 可池化对象接口
|
||||
*/
|
||||
export interface IPoolable {
|
||||
/**
|
||||
* 重置对象状态,准备重用
|
||||
*/
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象池统计信息
|
||||
*/
|
||||
export interface PoolStats {
|
||||
/** 池中对象数量 */
|
||||
size: number;
|
||||
/** 池的最大大小 */
|
||||
maxSize: number;
|
||||
/** 总共创建的对象数量 */
|
||||
totalCreated: number;
|
||||
/** 总共获取的次数 */
|
||||
totalObtained: number;
|
||||
/** 总共释放的次数 */
|
||||
totalReleased: number;
|
||||
/** 命中率(从池中获取的比例) */
|
||||
hitRate: number;
|
||||
/** 内存使用估算(字节) */
|
||||
estimatedMemoryUsage: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高性能通用对象池
|
||||
* 支持任意类型的对象池化,包含详细的统计信息
|
||||
*/
|
||||
export class Pool<T extends IPoolable> {
|
||||
private static _pools = new Map<Function, Pool<any>>();
|
||||
|
||||
private _objects: T[] = [];
|
||||
private _createFn: () => T;
|
||||
private _maxSize: number;
|
||||
private _stats: PoolStats;
|
||||
private _objectSize: number; // 估算的单个对象大小
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param createFn 创建对象的函数
|
||||
* @param maxSize 池的最大大小,默认100
|
||||
* @param estimatedObjectSize 估算的单个对象大小(字节),默认1024
|
||||
*/
|
||||
constructor(createFn: () => T, maxSize: number = 100, estimatedObjectSize: number = 1024) {
|
||||
this._createFn = createFn;
|
||||
this._maxSize = maxSize;
|
||||
this._objectSize = estimatedObjectSize;
|
||||
this._stats = {
|
||||
size: 0,
|
||||
maxSize,
|
||||
totalCreated: 0,
|
||||
totalObtained: 0,
|
||||
totalReleased: 0,
|
||||
hitRate: 0,
|
||||
estimatedMemoryUsage: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定类型的对象池
|
||||
* @param type 对象类型
|
||||
* @param maxSize 池的最大大小
|
||||
* @param estimatedObjectSize 估算的单个对象大小
|
||||
* @returns 对象池实例
|
||||
*/
|
||||
public static getPool<T extends IPoolable>(
|
||||
type: new (...args: any[]) => T,
|
||||
maxSize: number = 100,
|
||||
estimatedObjectSize: number = 1024
|
||||
): Pool<T> {
|
||||
let pool = this._pools.get(type);
|
||||
|
||||
if (!pool) {
|
||||
pool = new Pool<T>(() => new type(), maxSize, estimatedObjectSize);
|
||||
this._pools.set(type, pool);
|
||||
}
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从池中获取对象
|
||||
* @returns 对象实例
|
||||
*/
|
||||
public obtain(): T {
|
||||
this._stats.totalObtained++;
|
||||
|
||||
if (this._objects.length > 0) {
|
||||
const obj = this._objects.pop()!;
|
||||
this._stats.size--;
|
||||
this._updateHitRate();
|
||||
this._updateMemoryUsage();
|
||||
return obj;
|
||||
}
|
||||
|
||||
// 池中没有对象,创建新的
|
||||
const obj = this._createFn();
|
||||
this._stats.totalCreated++;
|
||||
this._updateHitRate();
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象归还到池中
|
||||
* @param obj 要归还的对象
|
||||
*/
|
||||
public free(obj: T): void {
|
||||
if (this._objects.length < this._maxSize) {
|
||||
obj.reset();
|
||||
this._objects.push(obj);
|
||||
this._stats.size++;
|
||||
this._stats.totalReleased++;
|
||||
this._updateMemoryUsage();
|
||||
}
|
||||
// 如果池已满,对象会被丢弃(由GC回收)
|
||||
}
|
||||
|
||||
/**
|
||||
* 预热池,创建指定数量的对象
|
||||
* @param count 要创建的对象数量
|
||||
*/
|
||||
public warmUp(count: number): void {
|
||||
const targetSize = Math.min(count, this._maxSize);
|
||||
|
||||
while (this._objects.length < targetSize) {
|
||||
const obj = this._createFn();
|
||||
this._stats.totalCreated++;
|
||||
this._objects.push(obj);
|
||||
this._stats.size++;
|
||||
}
|
||||
|
||||
this._updateMemoryUsage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空池
|
||||
*/
|
||||
public clear(): void {
|
||||
this._objects.length = 0;
|
||||
this._stats.size = 0;
|
||||
this._updateMemoryUsage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池中对象数量
|
||||
*/
|
||||
public get size(): number {
|
||||
return this._objects.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池的最大大小
|
||||
*/
|
||||
public get maxSize(): number {
|
||||
return this._maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置池的最大大小
|
||||
*/
|
||||
public set maxSize(value: number) {
|
||||
this._maxSize = value;
|
||||
this._stats.maxSize = value;
|
||||
|
||||
// 如果当前池大小超过新的最大值,则移除多余的对象
|
||||
while (this._objects.length > this._maxSize) {
|
||||
this._objects.pop();
|
||||
this._stats.size--;
|
||||
}
|
||||
|
||||
this._updateMemoryUsage();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池的统计信息
|
||||
*/
|
||||
public getStats(): PoolStats {
|
||||
return { ...this._stats };
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置统计信息
|
||||
*/
|
||||
public resetStats(): void {
|
||||
this._stats.totalCreated = 0;
|
||||
this._stats.totalObtained = 0;
|
||||
this._stats.totalReleased = 0;
|
||||
this._stats.hitRate = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新命中率
|
||||
*/
|
||||
private _updateHitRate(): void {
|
||||
if (this._stats.totalObtained > 0) {
|
||||
const hits = this._stats.totalObtained - this._stats.totalCreated;
|
||||
this._stats.hitRate = hits / this._stats.totalObtained;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新内存使用估算
|
||||
*/
|
||||
private _updateMemoryUsage(): void {
|
||||
this._stats.estimatedMemoryUsage = this._stats.size * this._objectSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:从指定类型的池中获取对象
|
||||
* @param type 对象类型
|
||||
* @returns 对象实例
|
||||
*/
|
||||
public static obtain<T extends IPoolable>(type: new (...args: any[]) => T): T {
|
||||
return this.getPool(type).obtain();
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:将对象归还到对应类型的池中
|
||||
* @param type 对象类型
|
||||
* @param obj 要归还的对象
|
||||
*/
|
||||
public static free<T extends IPoolable>(type: new (...args: any[]) => T, obj: T): void {
|
||||
this.getPool(type).free(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:预热指定类型的池
|
||||
* @param type 对象类型
|
||||
* @param count 要创建的对象数量
|
||||
*/
|
||||
public static warmUp<T extends IPoolable>(type: new (...args: any[]) => T, count: number): void {
|
||||
this.getPool(type).warmUp(count);
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:清空指定类型的池
|
||||
* @param type 对象类型
|
||||
*/
|
||||
public static clearPool<T extends IPoolable>(type: new (...args: any[]) => T): void {
|
||||
const pool = this._pools.get(type);
|
||||
if (pool) {
|
||||
pool.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:清空所有池
|
||||
*/
|
||||
public static clearAllPools(): void {
|
||||
for (const pool of this._pools.values()) {
|
||||
pool.clear();
|
||||
}
|
||||
this._pools.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:获取池的统计信息
|
||||
* @returns 池的统计信息
|
||||
*/
|
||||
public static getStats(): { [typeName: string]: PoolStats } {
|
||||
const stats: { [typeName: string]: PoolStats } = {};
|
||||
|
||||
for (const [type, pool] of this._pools.entries()) {
|
||||
const typeName = (type as any).name || 'Unknown';
|
||||
stats[typeName] = pool.getStats();
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:获取所有池的总内存使用量
|
||||
* @returns 总内存使用量(字节)
|
||||
*/
|
||||
public static getTotalMemoryUsage(): number {
|
||||
let total = 0;
|
||||
for (const pool of this._pools.values()) {
|
||||
total += pool.getStats().estimatedMemoryUsage;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:获取性能报告
|
||||
* @returns 格式化的性能报告
|
||||
*/
|
||||
public static getPerformanceReport(): string {
|
||||
const stats = this.getStats();
|
||||
const lines: string[] = [];
|
||||
|
||||
lines.push('=== Object Pool Performance Report ===');
|
||||
lines.push(`Total Memory Usage: ${(this.getTotalMemoryUsage() / 1024 / 1024).toFixed(2)} MB`);
|
||||
lines.push('');
|
||||
|
||||
for (const [typeName, stat] of Object.entries(stats)) {
|
||||
lines.push(`${typeName}:`);
|
||||
lines.push(` Size: ${stat.size}/${stat.maxSize}`);
|
||||
lines.push(` Hit Rate: ${(stat.hitRate * 100).toFixed(1)}%`);
|
||||
lines.push(` Total Created: ${stat.totalCreated}`);
|
||||
lines.push(` Total Obtained: ${stat.totalObtained}`);
|
||||
lines.push(` Memory: ${(stat.estimatedMemoryUsage / 1024).toFixed(1)} KB`);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分层对象池
|
||||
* 使用多个不同大小的池来优化内存使用
|
||||
*/
|
||||
export class TieredObjectPool<T extends IPoolable> {
|
||||
private pools: Pool<T>[] = [];
|
||||
private createFn: () => T;
|
||||
private resetFn: (obj: T) => void;
|
||||
private tierSizes: number[];
|
||||
private totalObtained = 0;
|
||||
private totalReleased = 0;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param createFn 创建对象的函数
|
||||
* @param resetFn 重置对象的函数
|
||||
* @param tierSizes 各层级的大小,默认[10, 50, 200]
|
||||
* @param estimatedObjectSize 估算的单个对象大小
|
||||
*/
|
||||
constructor(
|
||||
createFn: () => T,
|
||||
resetFn: (obj: T) => void,
|
||||
tierSizes: number[] = [10, 50, 200],
|
||||
estimatedObjectSize: number = 1024
|
||||
) {
|
||||
this.createFn = createFn;
|
||||
this.resetFn = resetFn;
|
||||
this.tierSizes = tierSizes;
|
||||
|
||||
// 初始化不同层级的池
|
||||
for (const size of tierSizes) {
|
||||
this.pools.push(new Pool(createFn, size, estimatedObjectSize));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象
|
||||
* @returns 对象实例
|
||||
*/
|
||||
public obtain(): T {
|
||||
this.totalObtained++;
|
||||
|
||||
// 从最小的池开始尝试获取
|
||||
for (const pool of this.pools) {
|
||||
if (pool.size > 0) {
|
||||
return pool.obtain();
|
||||
}
|
||||
}
|
||||
|
||||
// 所有池都空了,创建新对象
|
||||
return this.createFn();
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放对象
|
||||
* @param obj 要释放的对象
|
||||
*/
|
||||
public release(obj: T): void {
|
||||
this.totalReleased++;
|
||||
this.resetFn(obj);
|
||||
|
||||
// 放入第一个有空间的池
|
||||
for (const pool of this.pools) {
|
||||
if (pool.size < pool.maxSize) {
|
||||
pool.free(obj);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 所有池都满了,直接丢弃
|
||||
}
|
||||
|
||||
/**
|
||||
* 预热所有池
|
||||
* @param totalCount 总预热数量
|
||||
*/
|
||||
public warmUp(totalCount: number): void {
|
||||
let remaining = totalCount;
|
||||
|
||||
for (const pool of this.pools) {
|
||||
const warmUpCount = Math.min(remaining, pool.maxSize);
|
||||
pool.warmUp(warmUpCount);
|
||||
remaining -= warmUpCount;
|
||||
|
||||
if (remaining <= 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有池
|
||||
*/
|
||||
public clear(): void {
|
||||
for (const pool of this.pools) {
|
||||
pool.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
public getStats(): {
|
||||
totalSize: number;
|
||||
totalMaxSize: number;
|
||||
totalMemoryUsage: number;
|
||||
tierStats: PoolStats[];
|
||||
hitRate: number;
|
||||
} {
|
||||
let totalSize = 0;
|
||||
let totalMaxSize = 0;
|
||||
let totalMemoryUsage = 0;
|
||||
const tierStats: PoolStats[] = [];
|
||||
|
||||
for (const pool of this.pools) {
|
||||
const stats = pool.getStats();
|
||||
tierStats.push(stats);
|
||||
totalSize += stats.size;
|
||||
totalMaxSize += stats.maxSize;
|
||||
totalMemoryUsage += stats.estimatedMemoryUsage;
|
||||
}
|
||||
|
||||
const hitRate = this.totalObtained > 0 ?
|
||||
(this.totalObtained - this.getTotalCreated()) / this.totalObtained : 0;
|
||||
|
||||
return {
|
||||
totalSize,
|
||||
totalMaxSize,
|
||||
totalMemoryUsage,
|
||||
tierStats,
|
||||
hitRate
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取总创建数量
|
||||
*/
|
||||
private getTotalCreated(): number {
|
||||
return this.pools.reduce((total, pool) => total + pool.getStats().totalCreated, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 池管理器
|
||||
* 统一管理所有对象池
|
||||
*/
|
||||
export class PoolManager {
|
||||
private static instance: PoolManager;
|
||||
private pools = new Map<string, Pool<any> | TieredObjectPool<any>>();
|
||||
private autoCompactInterval = 60000; // 60秒
|
||||
private lastCompactTime = 0;
|
||||
|
||||
public static getInstance(): PoolManager {
|
||||
if (!PoolManager.instance) {
|
||||
PoolManager.instance = new PoolManager();
|
||||
}
|
||||
return PoolManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册池
|
||||
* @param name 池名称
|
||||
* @param pool 池实例
|
||||
*/
|
||||
public registerPool<T extends IPoolable>(name: string, pool: Pool<T> | TieredObjectPool<T>): void {
|
||||
this.pools.set(name, pool);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取池
|
||||
* @param name 池名称
|
||||
* @returns 池实例
|
||||
*/
|
||||
public getPool<T extends IPoolable>(name: string): Pool<T> | TieredObjectPool<T> | null {
|
||||
return this.pools.get(name) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新池管理器(应在游戏循环中调用)
|
||||
*/
|
||||
public update(): void {
|
||||
const now = Date.now();
|
||||
|
||||
if (now - this.lastCompactTime > this.autoCompactInterval) {
|
||||
this.compactAllPools();
|
||||
this.lastCompactTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 压缩所有池(清理碎片)
|
||||
*/
|
||||
public compactAllPools(): void {
|
||||
// 对于标准池,可以考虑清理一些长时间未使用的对象
|
||||
// 这里简单实现为重置统计信息
|
||||
for (const pool of this.pools.values()) {
|
||||
if (pool instanceof Pool) {
|
||||
pool.resetStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有池的统计信息
|
||||
*/
|
||||
public getAllStats(): Map<string, any> {
|
||||
const stats = new Map<string, any>();
|
||||
|
||||
for (const [name, pool] of this.pools.entries()) {
|
||||
if (pool instanceof Pool) {
|
||||
stats.set(name, pool.getStats());
|
||||
} else if (pool instanceof TieredObjectPool) {
|
||||
stats.set(name, pool.getStats());
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成性能报告
|
||||
*/
|
||||
public generateReport(): string {
|
||||
const lines: string[] = [];
|
||||
lines.push('=== Pool Manager Report ===');
|
||||
|
||||
let totalMemory = 0;
|
||||
|
||||
for (const [name, pool] of this.pools.entries()) {
|
||||
lines.push(`\n${name}:`);
|
||||
|
||||
if (pool instanceof Pool) {
|
||||
const stats = pool.getStats();
|
||||
lines.push(` Type: Standard Pool`);
|
||||
lines.push(` Size: ${stats.size}/${stats.maxSize}`);
|
||||
lines.push(` Hit Rate: ${(stats.hitRate * 100).toFixed(1)}%`);
|
||||
lines.push(` Memory: ${(stats.estimatedMemoryUsage / 1024).toFixed(1)} KB`);
|
||||
totalMemory += stats.estimatedMemoryUsage;
|
||||
} else if (pool instanceof TieredObjectPool) {
|
||||
const stats = pool.getStats();
|
||||
lines.push(` Type: Tiered Pool`);
|
||||
lines.push(` Total Size: ${stats.totalSize}/${stats.totalMaxSize}`);
|
||||
lines.push(` Hit Rate: ${(stats.hitRate * 100).toFixed(1)}%`);
|
||||
lines.push(` Memory: ${(stats.totalMemoryUsage / 1024).toFixed(1)} KB`);
|
||||
totalMemory += stats.totalMemoryUsage;
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(`\nTotal Memory Usage: ${(totalMemory / 1024 / 1024).toFixed(2)} MB`);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
module es {
|
||||
export class Ref<T> {
|
||||
public value: T;
|
||||
|
||||
constructor(value: T) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
module es {
|
||||
export class Screen {
|
||||
public static width: number;
|
||||
public static height: number;
|
||||
import { Vector2 } from '../Math/Vector2';
|
||||
|
||||
public static get size(): Vector2 {
|
||||
return new Vector2(this.width, this.height);
|
||||
}
|
||||
/**
|
||||
* 屏幕工具类
|
||||
* 提供屏幕尺寸相关的功能
|
||||
*/
|
||||
export class Screen {
|
||||
public static width: number;
|
||||
public static height: number;
|
||||
|
||||
public static get center(): Vector2 {
|
||||
return new Vector2(this.width / 2, this.height / 2);
|
||||
}
|
||||
public static get size(): Vector2 {
|
||||
return new Vector2(this.width, this.height);
|
||||
}
|
||||
|
||||
public static get center(): Vector2 {
|
||||
return new Vector2(this.width / 2, this.height / 2);
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 管理数值的简单助手类。它存储值,直到累计的总数大于1。一旦超过1,该值将在调用update时添加到amount中。
|
||||
*/
|
||||
export class SubpixelNumber {
|
||||
public remainder: number;
|
||||
|
||||
/**
|
||||
* 以amount递增余数,将值截断为int,存储新的余数并将amount设置为当前值。
|
||||
* @param amount
|
||||
*/
|
||||
public update(amount: number){
|
||||
this.remainder += amount;
|
||||
let motion = Math.trunc(this.remainder);
|
||||
this.remainder -= motion;
|
||||
return motion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将余数重置为0。当一个物体与一个不可移动的物体碰撞时有用。
|
||||
* 在这种情况下,您将希望将亚像素余数归零,因为它是空的和无效的碰撞。
|
||||
*/
|
||||
public reset(){
|
||||
this.remainder = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
92
source/src/Utils/Time.ts
Normal file
92
source/src/Utils/Time.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 时间管理工具类
|
||||
* 提供游戏时间相关的功能,包括帧时间、总时间、时间缩放等
|
||||
*/
|
||||
export class Time {
|
||||
/**
|
||||
* 上一帧到当前帧的时间间隔(秒)
|
||||
*/
|
||||
public static deltaTime: number = 0;
|
||||
|
||||
/**
|
||||
* 未缩放的帧时间间隔(秒)
|
||||
*/
|
||||
public static unscaledDeltaTime: number = 0;
|
||||
|
||||
/**
|
||||
* 游戏开始以来的总时间(秒)
|
||||
*/
|
||||
public static totalTime: number = 0;
|
||||
|
||||
/**
|
||||
* 未缩放的总时间(秒)
|
||||
*/
|
||||
public static unscaledTotalTime: number = 0;
|
||||
|
||||
/**
|
||||
* 时间缩放比例
|
||||
*/
|
||||
public static timeScale: number = 1;
|
||||
|
||||
/**
|
||||
* 当前帧数
|
||||
*/
|
||||
public static frameCount: number = 0;
|
||||
|
||||
/**
|
||||
* 上一帧的时间戳
|
||||
*/
|
||||
private static _lastTime: number = 0;
|
||||
|
||||
/**
|
||||
* 是否为第一次更新
|
||||
*/
|
||||
private static _isFirstUpdate: boolean = true;
|
||||
|
||||
/**
|
||||
* 更新时间信息
|
||||
* @param currentTime 当前时间戳(毫秒)
|
||||
*/
|
||||
public static update(currentTime: number = -1): void {
|
||||
if (currentTime === -1) {
|
||||
currentTime = Date.now();
|
||||
}
|
||||
|
||||
if (this._isFirstUpdate) {
|
||||
this._lastTime = currentTime;
|
||||
this._isFirstUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算帧时间间隔(转换为秒)
|
||||
this.unscaledDeltaTime = (currentTime - this._lastTime) / 1000;
|
||||
this.deltaTime = this.unscaledDeltaTime * this.timeScale;
|
||||
|
||||
// 更新总时间
|
||||
this.unscaledTotalTime += this.unscaledDeltaTime;
|
||||
this.totalTime += this.deltaTime;
|
||||
|
||||
// 更新帧数
|
||||
this.frameCount++;
|
||||
|
||||
// 记录当前时间
|
||||
this._lastTime = currentTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景改变时重置时间
|
||||
*/
|
||||
public static sceneChanged(): void {
|
||||
this._isFirstUpdate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定的时间间隔是否已经过去
|
||||
* @param interval 时间间隔(秒)
|
||||
* @param lastTime 上次检查的时间
|
||||
* @returns 是否已经过去指定时间
|
||||
*/
|
||||
public static checkEvery(interval: number, lastTime: number): boolean {
|
||||
return this.totalTime - lastTime >= interval;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,18 @@
|
||||
module es {
|
||||
export interface ITimer {
|
||||
context: any;
|
||||
export interface ITimer {
|
||||
context: any;
|
||||
|
||||
/**
|
||||
* 调用stop以停止此计时器再次运行。这对非重复计时器没有影响。
|
||||
*/
|
||||
stop();
|
||||
/**
|
||||
* 调用stop以停止此计时器再次运行。这对非重复计时器没有影响。
|
||||
*/
|
||||
stop(): void;
|
||||
|
||||
/**
|
||||
* 将计时器的运行时间重置为0
|
||||
*/
|
||||
reset();
|
||||
/**
|
||||
* 将计时器的运行时间重置为0
|
||||
*/
|
||||
reset(): void;
|
||||
|
||||
/**
|
||||
* 返回投向T的上下文,作为方便
|
||||
*/
|
||||
getContext<T>(): T;
|
||||
}
|
||||
/**
|
||||
* 返回投向T的上下文,作为方便
|
||||
*/
|
||||
getContext<T>(): T;
|
||||
}
|
||||
@@ -1,55 +1,56 @@
|
||||
module es {
|
||||
import { ITimer } from './ITimer';
|
||||
import { Time } from '../Time';
|
||||
|
||||
/**
|
||||
* 私有类隐藏ITimer的实现
|
||||
*/
|
||||
export class Timer implements ITimer{
|
||||
public context: any;
|
||||
public _timeInSeconds: number = 0;
|
||||
public _repeats: boolean = false;
|
||||
public _onTime!: (timer: ITimer) => void;
|
||||
public _isDone: boolean = false;
|
||||
public _elapsedTime: number = 0;
|
||||
|
||||
public getContext<T>(): T {
|
||||
return this.context as T;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this._elapsedTime = 0;
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
this._isDone = true;
|
||||
}
|
||||
|
||||
public tick(){
|
||||
// 如果stop在tick之前被调用,那么isDone将为true,我们不应该再做任何事情
|
||||
if (!this._isDone && this._elapsedTime > this._timeInSeconds){
|
||||
this._elapsedTime -= this._timeInSeconds;
|
||||
this._onTime(this);
|
||||
|
||||
if (!this._isDone && !this._repeats)
|
||||
this._isDone = true;
|
||||
}
|
||||
|
||||
this._elapsedTime += Time.deltaTime;
|
||||
|
||||
return this._isDone;
|
||||
}
|
||||
|
||||
public initialize(timeInsSeconds: number, repeats: boolean, context: any, onTime: (timer: ITimer)=>void){
|
||||
this._timeInSeconds = timeInsSeconds;
|
||||
this._repeats = repeats;
|
||||
this.context = context;
|
||||
this._onTime = onTime.bind(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有类隐藏ITimer的实现
|
||||
* 空出对象引用,以便在js需要时GC可以清理它们的引用
|
||||
*/
|
||||
export class Timer implements ITimer{
|
||||
public context: any;
|
||||
public _timeInSeconds: number = 0;
|
||||
public _repeats: boolean = false;
|
||||
public _onTime: (timer: ITimer) => void;
|
||||
public _isDone: boolean = false;
|
||||
public _elapsedTime: number = 0;
|
||||
|
||||
public getContext<T>(): T {
|
||||
return this.context as T;
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this._elapsedTime = 0;
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this._isDone = true;
|
||||
}
|
||||
|
||||
public tick(){
|
||||
// 如果stop在tick之前被调用,那么isDone将为true,我们不应该再做任何事情
|
||||
if (!this._isDone && this._elapsedTime > this._timeInSeconds){
|
||||
this._elapsedTime -= this._timeInSeconds;
|
||||
this._onTime(this);
|
||||
|
||||
if (!this._isDone && !this._repeats)
|
||||
this._isDone = true;
|
||||
}
|
||||
|
||||
this._elapsedTime += Time.deltaTime;
|
||||
|
||||
return this._isDone;
|
||||
}
|
||||
|
||||
public initialize(timeInsSeconds: number, repeats: boolean, context: any, onTime: (timer: ITimer)=>void){
|
||||
this._timeInSeconds = timeInsSeconds;
|
||||
this._repeats = repeats;
|
||||
this.context = context;
|
||||
this._onTime = onTime.bind(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* 空出对象引用,以便在js需要时GC可以清理它们的引用
|
||||
*/
|
||||
public unload(){
|
||||
this.context = null;
|
||||
this._onTime = null;
|
||||
}
|
||||
public unload(){
|
||||
this.context = null;
|
||||
this._onTime = null as any;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +1,34 @@
|
||||
module es {
|
||||
/**
|
||||
* 允许动作的延迟和重复执行
|
||||
*/
|
||||
export class TimerManager extends GlobalManager {
|
||||
public _timers: Timer[] = [];
|
||||
import { GlobalManager } from '../GlobalManager';
|
||||
import { Timer } from './Timer';
|
||||
import { ITimer } from './ITimer';
|
||||
|
||||
public update() {
|
||||
for (let i = this._timers.length - 1; i >= 0; i --){
|
||||
if (this._timers[i].tick()){
|
||||
this._timers[i].unload();
|
||||
this._timers.splice(i, 1);
|
||||
}
|
||||
/**
|
||||
* 允许动作的延迟和重复执行
|
||||
*/
|
||||
export class TimerManager extends GlobalManager {
|
||||
public _timers: Timer[] = [];
|
||||
|
||||
public override update() {
|
||||
for (let i = this._timers.length - 1; i >= 0; i --){
|
||||
if (this._timers[i].tick()){
|
||||
this._timers[i].unload();
|
||||
this._timers.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调度一个一次性或重复的计时器,该计时器将调用已传递的动作
|
||||
* @param timeInSeconds
|
||||
* @param repeats
|
||||
* @param context
|
||||
* @param onTime
|
||||
*/
|
||||
public schedule(timeInSeconds: number, repeats: boolean, context: any, onTime: (timer: ITimer)=>void){
|
||||
let timer = new Timer();
|
||||
timer.initialize(timeInSeconds, repeats, context, onTime);
|
||||
this._timers.push(timer);
|
||||
/**
|
||||
* 调度一个一次性或重复的计时器,该计时器将调用已传递的动作
|
||||
* @param timeInSeconds
|
||||
* @param repeats
|
||||
* @param context
|
||||
* @param onTime
|
||||
*/
|
||||
public schedule(timeInSeconds: number, repeats: boolean, context: any, onTime: (timer: ITimer)=>void){
|
||||
let timer = new Timer();
|
||||
timer.initialize(timeInSeconds, repeats, context, onTime);
|
||||
this._timers.push(timer);
|
||||
|
||||
return timer;
|
||||
}
|
||||
return timer;
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
module es {
|
||||
/**
|
||||
* 简单的剪耳三角测量器,最终的三角形将出现在triangleIndices列表中。
|
||||
*/
|
||||
export class Triangulator {
|
||||
/**
|
||||
* 上次三角函数调用中使用的点列表的三角列表条目索引
|
||||
*/
|
||||
public triangleIndices: number[] = [];
|
||||
|
||||
private _triPrev: number[] = new Array<number>(12);
|
||||
private _triNext: number[] = new Array<number>(12);
|
||||
|
||||
public static testPointTriangle(point: Vector2, a: Vector2, b: Vector2, c: Vector2): boolean {
|
||||
// 如果点在AB的右边,那么外边的三角形是
|
||||
if (Vector2Ext.cross(point.sub(a), b.sub(a)) < 0)
|
||||
return false;
|
||||
|
||||
// 如果点在BC的右边,则在三角形的外侧
|
||||
if (Vector2Ext.cross(point.sub(b), c.sub(b)) < 0)
|
||||
return false;
|
||||
|
||||
// 如果点在ca的右边,则在三角形的外面
|
||||
if (Vector2Ext.cross(point.sub(c), a.sub(c)) < 0)
|
||||
return false;
|
||||
|
||||
// 点在三角形上
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算一个三角形列表,该列表完全覆盖给定点集所包含的区域。如果点不是CCW,则将arePointsCCW参数传递为false
|
||||
* @param points 定义封闭路径的点列表
|
||||
* @param arePointsCCW
|
||||
*/
|
||||
public triangulate(points: Vector2[], arePointsCCW: boolean = true) {
|
||||
let count = points.length;
|
||||
|
||||
// 设置前一个链接和下一个链接
|
||||
this.initialize(count);
|
||||
|
||||
// 非三角的多边形断路器
|
||||
let iterations = 0;
|
||||
|
||||
// 从0开始
|
||||
let index = 0;
|
||||
|
||||
// 继续移除所有的三角形,直到只剩下一个三角形
|
||||
while (count > 3 && iterations < 500) {
|
||||
iterations++;
|
||||
|
||||
let isEar = true;
|
||||
|
||||
let a = points[this._triPrev[index]];
|
||||
let b = points[index];
|
||||
let c = points[this._triNext[index]];
|
||||
|
||||
if (Vector2Ext.isTriangleCCW(a, b, c)) {
|
||||
let k = this._triNext[this._triNext[index]];
|
||||
do {
|
||||
if (Triangulator.testPointTriangle(points[k], a, b, c)) {
|
||||
isEar = false;
|
||||
break;
|
||||
}
|
||||
|
||||
k = this._triNext[k];
|
||||
} while (k != this._triPrev[index]);
|
||||
} else {
|
||||
isEar = false;
|
||||
}
|
||||
|
||||
if (isEar) {
|
||||
this.triangleIndices.push(this._triPrev[index]);
|
||||
this.triangleIndices.push(index);
|
||||
this.triangleIndices.push(this._triNext[index]);
|
||||
|
||||
// 删除vert通过重定向相邻vert的上一个和下一个链接,从而减少vertext计数
|
||||
this._triNext[this._triPrev[index]] = this._triNext[index];
|
||||
this._triPrev[this._triNext[index]] = this._triPrev[index];
|
||||
count--;
|
||||
|
||||
// 接下来访问前一个vert
|
||||
index = this._triPrev[index];
|
||||
} else {
|
||||
index = this._triNext[index];
|
||||
}
|
||||
}
|
||||
|
||||
this.triangleIndices.push(this._triPrev[index]);
|
||||
this.triangleIndices.push(index);
|
||||
this.triangleIndices.push(this._triNext[index]);
|
||||
|
||||
if (!arePointsCCW)
|
||||
this.triangleIndices.reverse();
|
||||
}
|
||||
|
||||
private initialize(count: number) {
|
||||
this.triangleIndices.length = 0;
|
||||
|
||||
if (this._triNext.length < count) {
|
||||
this._triNext.reverse();
|
||||
this._triNext.length = Math.max(this._triNext.length * 2, count);
|
||||
}
|
||||
if (this._triPrev.length < count) {
|
||||
this._triPrev.reverse();
|
||||
this._triPrev.length = Math.max(this._triPrev.length * 2, count);
|
||||
}
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
this._triPrev[i] = i - 1;
|
||||
this._triNext[i] = i + 1;
|
||||
}
|
||||
|
||||
this._triPrev[0] = count - 1;
|
||||
this._triNext[count - 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
module es {
|
||||
const hex = [
|
||||
// hex identity values 0-255
|
||||
"00",
|
||||
"01",
|
||||
"02",
|
||||
"03",
|
||||
"04",
|
||||
"05",
|
||||
"06",
|
||||
"07",
|
||||
"08",
|
||||
"09",
|
||||
"0a",
|
||||
"0b",
|
||||
"0c",
|
||||
"0d",
|
||||
"0e",
|
||||
"0f",
|
||||
"10",
|
||||
"11",
|
||||
"12",
|
||||
"13",
|
||||
"14",
|
||||
"15",
|
||||
"16",
|
||||
"17",
|
||||
"18",
|
||||
"19",
|
||||
"1a",
|
||||
"1b",
|
||||
"1c",
|
||||
"1d",
|
||||
"1e",
|
||||
"1f",
|
||||
"20",
|
||||
"21",
|
||||
"22",
|
||||
"23",
|
||||
"24",
|
||||
"25",
|
||||
"26",
|
||||
"27",
|
||||
"28",
|
||||
"29",
|
||||
"2a",
|
||||
"2b",
|
||||
"2c",
|
||||
"2d",
|
||||
"2e",
|
||||
"2f",
|
||||
"30",
|
||||
"31",
|
||||
"32",
|
||||
"33",
|
||||
"34",
|
||||
"35",
|
||||
"36",
|
||||
"37",
|
||||
"38",
|
||||
"39",
|
||||
"3a",
|
||||
"3b",
|
||||
"3c",
|
||||
"3d",
|
||||
"3e",
|
||||
"3f",
|
||||
"40",
|
||||
"41",
|
||||
"42",
|
||||
"43",
|
||||
"44",
|
||||
"45",
|
||||
"46",
|
||||
"47",
|
||||
"48",
|
||||
"49",
|
||||
"4a",
|
||||
"4b",
|
||||
"4c",
|
||||
"4d",
|
||||
"4e",
|
||||
"4f",
|
||||
"50",
|
||||
"51",
|
||||
"52",
|
||||
"53",
|
||||
"54",
|
||||
"55",
|
||||
"56",
|
||||
"57",
|
||||
"58",
|
||||
"59",
|
||||
"5a",
|
||||
"5b",
|
||||
"5c",
|
||||
"5d",
|
||||
"5e",
|
||||
"5f",
|
||||
"60",
|
||||
"61",
|
||||
"62",
|
||||
"63",
|
||||
"64",
|
||||
"65",
|
||||
"66",
|
||||
"67",
|
||||
"68",
|
||||
"69",
|
||||
"6a",
|
||||
"6b",
|
||||
"6c",
|
||||
"6d",
|
||||
"6e",
|
||||
"6f",
|
||||
"70",
|
||||
"71",
|
||||
"72",
|
||||
"73",
|
||||
"74",
|
||||
"75",
|
||||
"76",
|
||||
"77",
|
||||
"78",
|
||||
"79",
|
||||
"7a",
|
||||
"7b",
|
||||
"7c",
|
||||
"7d",
|
||||
"7e",
|
||||
"7f",
|
||||
"80",
|
||||
"81",
|
||||
"82",
|
||||
"83",
|
||||
"84",
|
||||
"85",
|
||||
"86",
|
||||
"87",
|
||||
"88",
|
||||
"89",
|
||||
"8a",
|
||||
"8b",
|
||||
"8c",
|
||||
"8d",
|
||||
"8e",
|
||||
"8f",
|
||||
"90",
|
||||
"91",
|
||||
"92",
|
||||
"93",
|
||||
"94",
|
||||
"95",
|
||||
"96",
|
||||
"97",
|
||||
"98",
|
||||
"99",
|
||||
"9a",
|
||||
"9b",
|
||||
"9c",
|
||||
"9d",
|
||||
"9e",
|
||||
"9f",
|
||||
"a0",
|
||||
"a1",
|
||||
"a2",
|
||||
"a3",
|
||||
"a4",
|
||||
"a5",
|
||||
"a6",
|
||||
"a7",
|
||||
"a8",
|
||||
"a9",
|
||||
"aa",
|
||||
"ab",
|
||||
"ac",
|
||||
"ad",
|
||||
"ae",
|
||||
"af",
|
||||
"b0",
|
||||
"b1",
|
||||
"b2",
|
||||
"b3",
|
||||
"b4",
|
||||
"b5",
|
||||
"b6",
|
||||
"b7",
|
||||
"b8",
|
||||
"b9",
|
||||
"ba",
|
||||
"bb",
|
||||
"bc",
|
||||
"bd",
|
||||
"be",
|
||||
"bf",
|
||||
"c0",
|
||||
"c1",
|
||||
"c2",
|
||||
"c3",
|
||||
"c4",
|
||||
"c5",
|
||||
"c6",
|
||||
"c7",
|
||||
"c8",
|
||||
"c9",
|
||||
"ca",
|
||||
"cb",
|
||||
"cc",
|
||||
"cd",
|
||||
"ce",
|
||||
"cf",
|
||||
"d0",
|
||||
"d1",
|
||||
"d2",
|
||||
"d3",
|
||||
"d4",
|
||||
"d5",
|
||||
"d6",
|
||||
"d7",
|
||||
"d8",
|
||||
"d9",
|
||||
"da",
|
||||
"db",
|
||||
"dc",
|
||||
"dd",
|
||||
"de",
|
||||
"df",
|
||||
"e0",
|
||||
"e1",
|
||||
"e2",
|
||||
"e3",
|
||||
"e4",
|
||||
"e5",
|
||||
"e6",
|
||||
"e7",
|
||||
"e8",
|
||||
"e9",
|
||||
"ea",
|
||||
"eb",
|
||||
"ec",
|
||||
"ed",
|
||||
"ee",
|
||||
"ef",
|
||||
"f0",
|
||||
"f1",
|
||||
"f2",
|
||||
"f3",
|
||||
"f4",
|
||||
"f5",
|
||||
"f6",
|
||||
"f7",
|
||||
"f8",
|
||||
"f9",
|
||||
"fa",
|
||||
"fb",
|
||||
"fc",
|
||||
"fd",
|
||||
"fe",
|
||||
"ff",
|
||||
];
|
||||
|
||||
export class UUID {
|
||||
static randomUUID(): string {
|
||||
const d0 = (Math.random() * 0xffffffff) | 0;
|
||||
const d1 = (Math.random() * 0xffffffff) | 0;
|
||||
const d2 = (Math.random() * 0xffffffff) | 0;
|
||||
const d3 = (Math.random() * 0xffffffff) | 0;
|
||||
return (
|
||||
hex[d0 & 0xff] +
|
||||
hex[(d0 >> 8) & 0xff] +
|
||||
hex[(d0 >> 16) & 0xff] +
|
||||
hex[(d0 >> 24) & 0xff] +
|
||||
"-" +
|
||||
hex[d1 & 0xff] +
|
||||
hex[(d1 >> 8) & 0xff] +
|
||||
"-" +
|
||||
hex[((d1 >> 16) & 0x0f) | 0x40] +
|
||||
hex[(d1 >> 24) & 0xff] +
|
||||
"-" +
|
||||
hex[(d2 & 0x3f) | 0x80] +
|
||||
hex[(d2 >> 8) & 0xff] +
|
||||
"-" +
|
||||
hex[(d2 >> 16) & 0xff] +
|
||||
hex[(d2 >> 24) & 0xff] +
|
||||
hex[d3 & 0xff] +
|
||||
hex[(d3 >> 8) & 0xff] +
|
||||
hex[(d3 >> 16) & 0xff] +
|
||||
hex[(d3 >> 24) & 0xff]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
6
source/src/Utils/index.ts
Normal file
6
source/src/Utils/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// 工具类导出
|
||||
export * from './Extensions';
|
||||
export * from './Pool';
|
||||
export * from './PerformanceMonitor';
|
||||
export { Screen } from './Screen';
|
||||
export { Time } from './Time';
|
||||
@@ -1,7 +0,0 @@
|
||||
module es {
|
||||
export interface Class extends Function {}
|
||||
|
||||
export function getClassName(klass): string {
|
||||
return klass.className || klass.name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user