Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/gpu/objectCollectionBuffer.ts
3294 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Emitter, Event } from '../../../base/common/event.js';
7
import { Disposable, dispose, toDisposable, type IDisposable } from '../../../base/common/lifecycle.js';
8
import { LinkedList } from '../../../base/common/linkedList.js';
9
import { BufferDirtyTracker, type IBufferDirtyTrackerReader } from './bufferDirtyTracker.js';
10
11
export interface ObjectCollectionBufferPropertySpec {
12
name: string;
13
}
14
15
export type ObjectCollectionPropertyValues<T extends ObjectCollectionBufferPropertySpec[]> = {
16
[K in T[number]['name']]: number;
17
};
18
19
export interface IObjectCollectionBuffer<T extends ObjectCollectionBufferPropertySpec[]> extends IDisposable {
20
/**
21
* The underlying buffer. This **should not** be modified externally.
22
*/
23
readonly buffer: ArrayBufferLike;
24
/**
25
* A view of the underlying buffer. This **should not** be modified externally.
26
*/
27
readonly view: Float32Array;
28
/**
29
* The size of the used portion of the buffer (in bytes).
30
*/
31
readonly bufferUsedSize: number;
32
/**
33
* The size of the used portion of the view (in float32s).
34
*/
35
readonly viewUsedSize: number;
36
/**
37
* The number of entries in the buffer.
38
*/
39
readonly entryCount: number;
40
41
/**
42
* A tracker for dirty regions in the buffer.
43
*/
44
readonly dirtyTracker: IBufferDirtyTrackerReader;
45
46
/**
47
* Fires when the buffer is modified.
48
*/
49
readonly onDidChange: Event<void>;
50
51
/**
52
* Fires when the buffer is recreated.
53
*/
54
readonly onDidChangeBuffer: Event<void>;
55
56
/**
57
* Creates an entry in the collection. This will return a managed object that can be modified
58
* which will update the underlying buffer.
59
* @param data The data of the entry.
60
*/
61
createEntry(data: ObjectCollectionPropertyValues<T>): IObjectCollectionBufferEntry<T>;
62
}
63
64
/**
65
* An entry in an {@link ObjectCollectionBuffer}. Property values on the entry can be changed and
66
* their values will be updated automatically in the buffer.
67
*/
68
export interface IObjectCollectionBufferEntry<T extends ObjectCollectionBufferPropertySpec[]> extends IDisposable {
69
set(propertyName: T[number]['name'], value: number): void;
70
get(propertyName: T[number]['name']): number;
71
setRaw(data: ArrayLike<number>): void;
72
}
73
74
export function createObjectCollectionBuffer<T extends ObjectCollectionBufferPropertySpec[]>(
75
propertySpecs: T,
76
capacity: number
77
): IObjectCollectionBuffer<T> {
78
return new ObjectCollectionBuffer<T>(propertySpecs, capacity);
79
}
80
81
class ObjectCollectionBuffer<T extends ObjectCollectionBufferPropertySpec[]> extends Disposable implements IObjectCollectionBuffer<T> {
82
buffer: ArrayBufferLike;
83
view: Float32Array;
84
85
get bufferUsedSize() {
86
return this.viewUsedSize * Float32Array.BYTES_PER_ELEMENT;
87
}
88
get viewUsedSize() {
89
return this._entries.size * this._entrySize;
90
}
91
get entryCount() {
92
return this._entries.size;
93
}
94
95
private _dirtyTracker = new BufferDirtyTracker();
96
get dirtyTracker(): IBufferDirtyTrackerReader { return this._dirtyTracker; }
97
98
private readonly _propertySpecsMap: Map<string, ObjectCollectionBufferPropertySpec & { offset: number }> = new Map();
99
private readonly _entrySize: number;
100
private readonly _entries: LinkedList<ObjectCollectionBufferEntry<T>> = new LinkedList();
101
102
private readonly _onDidChange = this._register(new Emitter<void>());
103
readonly onDidChange = this._onDidChange.event;
104
private readonly _onDidChangeBuffer = this._register(new Emitter<void>());
105
readonly onDidChangeBuffer = this._onDidChangeBuffer.event;
106
107
constructor(
108
public propertySpecs: T,
109
public capacity: number
110
) {
111
super();
112
113
this.view = new Float32Array(capacity * propertySpecs.length);
114
this.buffer = this.view.buffer;
115
this._entrySize = propertySpecs.length;
116
for (let i = 0; i < propertySpecs.length; i++) {
117
const spec = {
118
offset: i,
119
...propertySpecs[i]
120
};
121
this._propertySpecsMap.set(spec.name, spec);
122
}
123
this._register(toDisposable(() => dispose(this._entries)));
124
}
125
126
createEntry(data: ObjectCollectionPropertyValues<T>): IObjectCollectionBufferEntry<T> {
127
if (this._entries.size === this.capacity) {
128
this._expandBuffer();
129
this._onDidChangeBuffer.fire();
130
}
131
132
const value = new ObjectCollectionBufferEntry(this.view, this._propertySpecsMap, this._dirtyTracker, this._entries.size, data);
133
const removeFromEntries = this._entries.push(value);
134
const listeners: IDisposable[] = [];
135
listeners.push(Event.forward(value.onDidChange, this._onDidChange));
136
listeners.push(value.onWillDispose(() => {
137
const deletedEntryIndex = value.i;
138
removeFromEntries();
139
140
// Shift all entries after the deleted entry to the left
141
this.view.set(this.view.subarray(deletedEntryIndex * this._entrySize + 2, this._entries.size * this._entrySize + 2), deletedEntryIndex * this._entrySize);
142
143
// Update entries to reflect the new i
144
for (const entry of this._entries) {
145
if (entry.i > deletedEntryIndex) {
146
entry.i--;
147
}
148
}
149
this._dirtyTracker.flag(deletedEntryIndex, (this._entries.size - deletedEntryIndex) * this._entrySize);
150
dispose(listeners);
151
}));
152
return value;
153
}
154
155
private _expandBuffer() {
156
this.capacity *= 2;
157
const newView = new Float32Array(this.capacity * this._entrySize);
158
newView.set(this.view);
159
this.view = newView;
160
this.buffer = this.view.buffer;
161
}
162
}
163
164
class ObjectCollectionBufferEntry<T extends ObjectCollectionBufferPropertySpec[]> extends Disposable implements IObjectCollectionBufferEntry<T> {
165
166
private readonly _onDidChange = this._register(new Emitter<void>());
167
readonly onDidChange = this._onDidChange.event;
168
private readonly _onWillDispose = this._register(new Emitter<void>());
169
readonly onWillDispose = this._onWillDispose.event;
170
171
constructor(
172
private _view: Float32Array,
173
private _propertySpecsMap: Map<string, ObjectCollectionBufferPropertySpec & { offset: number }>,
174
private _dirtyTracker: BufferDirtyTracker,
175
public i: number,
176
data: ObjectCollectionPropertyValues<T>,
177
) {
178
super();
179
for (const propertySpec of this._propertySpecsMap.values()) {
180
this._view[this.i * this._propertySpecsMap.size + propertySpec.offset] = data[propertySpec.name as keyof typeof data];
181
}
182
this._dirtyTracker.flag(this.i * this._propertySpecsMap.size, this._propertySpecsMap.size);
183
}
184
185
override dispose() {
186
this._onWillDispose.fire();
187
super.dispose();
188
}
189
190
set(propertyName: T[number]['name'], value: number): void {
191
const i = this.i * this._propertySpecsMap.size + this._propertySpecsMap.get(propertyName)!.offset;
192
this._view[this._dirtyTracker.flag(i)] = value;
193
this._onDidChange.fire();
194
}
195
196
get(propertyName: T[number]['name']): number {
197
return this._view[this.i * this._propertySpecsMap.size + this._propertySpecsMap.get(propertyName)!.offset];
198
}
199
200
setRaw(data: ArrayLike<number>): void {
201
if (data.length !== this._propertySpecsMap.size) {
202
throw new Error(`Data length ${data.length} does not match the number of properties in the collection (${this._propertySpecsMap.size})`);
203
}
204
this._view.set(data, this.i * this._propertySpecsMap.size);
205
this._dirtyTracker.flag(this.i * this._propertySpecsMap.size, this._propertySpecsMap.size);
206
}
207
}
208
209