Path: blob/main/src/vs/editor/browser/gpu/objectCollectionBuffer.ts
3294 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Emitter, Event } from '../../../base/common/event.js';6import { Disposable, dispose, toDisposable, type IDisposable } from '../../../base/common/lifecycle.js';7import { LinkedList } from '../../../base/common/linkedList.js';8import { BufferDirtyTracker, type IBufferDirtyTrackerReader } from './bufferDirtyTracker.js';910export interface ObjectCollectionBufferPropertySpec {11name: string;12}1314export type ObjectCollectionPropertyValues<T extends ObjectCollectionBufferPropertySpec[]> = {15[K in T[number]['name']]: number;16};1718export interface IObjectCollectionBuffer<T extends ObjectCollectionBufferPropertySpec[]> extends IDisposable {19/**20* The underlying buffer. This **should not** be modified externally.21*/22readonly buffer: ArrayBufferLike;23/**24* A view of the underlying buffer. This **should not** be modified externally.25*/26readonly view: Float32Array;27/**28* The size of the used portion of the buffer (in bytes).29*/30readonly bufferUsedSize: number;31/**32* The size of the used portion of the view (in float32s).33*/34readonly viewUsedSize: number;35/**36* The number of entries in the buffer.37*/38readonly entryCount: number;3940/**41* A tracker for dirty regions in the buffer.42*/43readonly dirtyTracker: IBufferDirtyTrackerReader;4445/**46* Fires when the buffer is modified.47*/48readonly onDidChange: Event<void>;4950/**51* Fires when the buffer is recreated.52*/53readonly onDidChangeBuffer: Event<void>;5455/**56* Creates an entry in the collection. This will return a managed object that can be modified57* which will update the underlying buffer.58* @param data The data of the entry.59*/60createEntry(data: ObjectCollectionPropertyValues<T>): IObjectCollectionBufferEntry<T>;61}6263/**64* An entry in an {@link ObjectCollectionBuffer}. Property values on the entry can be changed and65* their values will be updated automatically in the buffer.66*/67export interface IObjectCollectionBufferEntry<T extends ObjectCollectionBufferPropertySpec[]> extends IDisposable {68set(propertyName: T[number]['name'], value: number): void;69get(propertyName: T[number]['name']): number;70setRaw(data: ArrayLike<number>): void;71}7273export function createObjectCollectionBuffer<T extends ObjectCollectionBufferPropertySpec[]>(74propertySpecs: T,75capacity: number76): IObjectCollectionBuffer<T> {77return new ObjectCollectionBuffer<T>(propertySpecs, capacity);78}7980class ObjectCollectionBuffer<T extends ObjectCollectionBufferPropertySpec[]> extends Disposable implements IObjectCollectionBuffer<T> {81buffer: ArrayBufferLike;82view: Float32Array;8384get bufferUsedSize() {85return this.viewUsedSize * Float32Array.BYTES_PER_ELEMENT;86}87get viewUsedSize() {88return this._entries.size * this._entrySize;89}90get entryCount() {91return this._entries.size;92}9394private _dirtyTracker = new BufferDirtyTracker();95get dirtyTracker(): IBufferDirtyTrackerReader { return this._dirtyTracker; }9697private readonly _propertySpecsMap: Map<string, ObjectCollectionBufferPropertySpec & { offset: number }> = new Map();98private readonly _entrySize: number;99private readonly _entries: LinkedList<ObjectCollectionBufferEntry<T>> = new LinkedList();100101private readonly _onDidChange = this._register(new Emitter<void>());102readonly onDidChange = this._onDidChange.event;103private readonly _onDidChangeBuffer = this._register(new Emitter<void>());104readonly onDidChangeBuffer = this._onDidChangeBuffer.event;105106constructor(107public propertySpecs: T,108public capacity: number109) {110super();111112this.view = new Float32Array(capacity * propertySpecs.length);113this.buffer = this.view.buffer;114this._entrySize = propertySpecs.length;115for (let i = 0; i < propertySpecs.length; i++) {116const spec = {117offset: i,118...propertySpecs[i]119};120this._propertySpecsMap.set(spec.name, spec);121}122this._register(toDisposable(() => dispose(this._entries)));123}124125createEntry(data: ObjectCollectionPropertyValues<T>): IObjectCollectionBufferEntry<T> {126if (this._entries.size === this.capacity) {127this._expandBuffer();128this._onDidChangeBuffer.fire();129}130131const value = new ObjectCollectionBufferEntry(this.view, this._propertySpecsMap, this._dirtyTracker, this._entries.size, data);132const removeFromEntries = this._entries.push(value);133const listeners: IDisposable[] = [];134listeners.push(Event.forward(value.onDidChange, this._onDidChange));135listeners.push(value.onWillDispose(() => {136const deletedEntryIndex = value.i;137removeFromEntries();138139// Shift all entries after the deleted entry to the left140this.view.set(this.view.subarray(deletedEntryIndex * this._entrySize + 2, this._entries.size * this._entrySize + 2), deletedEntryIndex * this._entrySize);141142// Update entries to reflect the new i143for (const entry of this._entries) {144if (entry.i > deletedEntryIndex) {145entry.i--;146}147}148this._dirtyTracker.flag(deletedEntryIndex, (this._entries.size - deletedEntryIndex) * this._entrySize);149dispose(listeners);150}));151return value;152}153154private _expandBuffer() {155this.capacity *= 2;156const newView = new Float32Array(this.capacity * this._entrySize);157newView.set(this.view);158this.view = newView;159this.buffer = this.view.buffer;160}161}162163class ObjectCollectionBufferEntry<T extends ObjectCollectionBufferPropertySpec[]> extends Disposable implements IObjectCollectionBufferEntry<T> {164165private readonly _onDidChange = this._register(new Emitter<void>());166readonly onDidChange = this._onDidChange.event;167private readonly _onWillDispose = this._register(new Emitter<void>());168readonly onWillDispose = this._onWillDispose.event;169170constructor(171private _view: Float32Array,172private _propertySpecsMap: Map<string, ObjectCollectionBufferPropertySpec & { offset: number }>,173private _dirtyTracker: BufferDirtyTracker,174public i: number,175data: ObjectCollectionPropertyValues<T>,176) {177super();178for (const propertySpec of this._propertySpecsMap.values()) {179this._view[this.i * this._propertySpecsMap.size + propertySpec.offset] = data[propertySpec.name as keyof typeof data];180}181this._dirtyTracker.flag(this.i * this._propertySpecsMap.size, this._propertySpecsMap.size);182}183184override dispose() {185this._onWillDispose.fire();186super.dispose();187}188189set(propertyName: T[number]['name'], value: number): void {190const i = this.i * this._propertySpecsMap.size + this._propertySpecsMap.get(propertyName)!.offset;191this._view[this._dirtyTracker.flag(i)] = value;192this._onDidChange.fire();193}194195get(propertyName: T[number]['name']): number {196return this._view[this.i * this._propertySpecsMap.size + this._propertySpecsMap.get(propertyName)!.offset];197}198199setRaw(data: ArrayLike<number>): void {200if (data.length !== this._propertySpecsMap.size) {201throw new Error(`Data length ${data.length} does not match the number of properties in the collection (${this._propertySpecsMap.size})`);202}203this._view.set(data, this.i * this._propertySpecsMap.size);204this._dirtyTracker.flag(this.i * this._propertySpecsMap.size, this._propertySpecsMap.size);205}206}207208209