Skip to content

Commit

Permalink
feat: undo/redo size limits (#595)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoecheza authored May 22, 2023
1 parent 6b4cfa4 commit e7ba7f7
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 24 deletions.
13 changes: 7 additions & 6 deletions packages/@dcl/inspector/src/components/FileInput/FileInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ function parseAccept(accept: PropTypes['accept']) {
export function FileInput(props: PropsWithChildren<PropTypes>) {
const { onDrop } = props
const inputRef = useRef<HTMLInputElement>(null)
const acceptExtensions = Object.values(props.accept ?? []).flat()

const [_, drop] = useDrop(
() => ({
accept: [NativeTypes.FILE],
drop(item: { files: File[] }) {
const acceptExtensions = Object.values(props.accept ?? []).flat()
const canDrop = item.files.every((file) => !!acceptExtensions.find((ext) => file.name.endsWith(ext)))
if (onDrop && canDrop) {
onDrop(item.files)
}
}
if (onDrop) onDrop(item.files)
},
canDrop(item: { files: File[] }) {
return item.files.every((file) => !!acceptExtensions.find((ext) => file.name.endsWith(ext)))
},
}),
[props]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,6 @@
background-color: var(--primary);
}

.ImportAsset .file-container .error {
color: var(--primary);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { useFileSystem } from '../../hooks/catalog/useFileSystem'
import './ImportAsset.css'
import classNames from 'classnames'

const ONE_MB_IN_BYTES = 1_048_576
const ONE_GB_IN_BYTES = ONE_MB_IN_BYTES * 1024

interface PropTypes {
onSave(): void
}
Expand Down Expand Up @@ -61,6 +64,8 @@ const ImportAsset = withSdk<PropTypes>(({ sdk, onSave }) => {
return packageName?.toLocaleLowerCase() === assetPackageName?.toLocaleLowerCase()
})

const validSize = (file?.size || 0) <= ONE_GB_IN_BYTES

return (
<div className="ImportAsset">
<FileInput disabled={!!file} onDrop={handleDrop} accept={{ 'model/gltf-binary': ['.gltf', '.glb'] }}>
Expand All @@ -85,17 +90,18 @@ const ImportAsset = withSdk<PropTypes>(({ sdk, onSave }) => {
<IoIosImage />
<div className="file-title">{file.name}</div>
</Container>
<div className={classNames({ error: !!invalidName })}>
<div className={classNames({ error: !!invalidName || !validSize })}>
<Block label="Asset Pack Name">
<TextField
label=""
value={assetPackageName}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => setAssetPackageName(event.target.value)}
/>
</Block>
<Button disabled={invalidName} onClick={handleSave}>
<Button disabled={invalidName || !validSize} onClick={handleSave}>
Save asset
</Button>
<span>{!validSize && 'Asset size must be under 1GB'}</span>
</div>
</div>
)}
Expand Down
34 changes: 18 additions & 16 deletions packages/@dcl/inspector/src/lib/data-layer/host/undo-redo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import upsertAsset from './upsert-asset'
import { FileSystemInterface } from '../types'
import { findPrevValue } from './utils/component'
import { UndoRedoBuffer } from './utils/undo-redo-buffer'

export type UndoRedoCrdt = { $case: 'crdt'; operations: CrdtOperation[] }
export type UndoRedoFile = { $case: 'file'; operations: FileOperation[] }
Expand All @@ -27,16 +28,20 @@ export type FileOperation = {

export type UndoRedo = UndoRedoFile | UndoRedoCrdt
export type UndoRedoOp = UndoRedo['operations'][0]
export type UndoRedoGetter = <T extends UndoRedoOp>(op: T) => T['newValue']

function getAndCleanArray<T = unknown>(arr: T[]): T[] {
return arr.splice(0, arr.length)
}

const isNil = (val: unknown) => val === null || val === undefined

const getUndoValue: UndoRedoGetter = (val) => val.prevValue
const getRedoValue: UndoRedoGetter = (val) => val.newValue

export function initUndoRedo(fs: FileSystemInterface, engine: IEngine, getComposite: () => CompositeDefinition) {
const undoList: UndoRedo[] = []
const redoList: UndoRedo[] = []
const undoList = new UndoRedoBuffer(1024, getUndoValue)
const redoList = new UndoRedoBuffer(1024, getRedoValue)
const crdtAcc: CrdtOperation[] = []

function onChange(
Expand All @@ -63,10 +68,7 @@ export function initUndoRedo(fs: FileSystemInterface, engine: IEngine, getCompos
}
}

async function undoRedoLogic(
message: UndoRedo,
getValue: <T extends UndoRedoOp>(op: T) => T['newValue']
): Promise<void> {
async function undoRedoLogic(message: UndoRedo, getValue: UndoRedoGetter): Promise<void> {
if (message.$case === 'crdt') {
for (const operation of message.operations) {
const component = engine.getComponent(
Expand All @@ -88,35 +90,35 @@ export function initUndoRedo(fs: FileSystemInterface, engine: IEngine, getCompos
}

return {
async redo() {
const msg = redoList.pop()
async undo() {
const msg = undoList.pop()
if (msg) {
undoList.push(msg)
await undoRedoLogic(msg, (val) => val.newValue)
redoList.push(msg)
await undoRedoLogic(msg, getUndoValue)
await engine.update(1 / 16)
}
getAndCleanArray(crdtAcc)
return { type: msg?.$case ?? '' }
},
async undo() {
const msg = undoList.pop()
async redo() {
const msg = redoList.pop()
if (msg) {
redoList.push(msg)
await undoRedoLogic(msg, (val) => val.prevValue)
undoList.push(msg)
await undoRedoLogic(msg, getRedoValue)
await engine.update(1 / 16)
}
getAndCleanArray(crdtAcc)
return { type: msg?.$case ?? '' }
},
onChange,
addUndoFile(operations: FileOperation[]) {
getAndCleanArray(redoList)
redoList.clear()
undoList.push({ $case: 'file', operations })
},
addUndoCrdt() {
const changes = getAndCleanArray(crdtAcc)
if (changes.length) {
getAndCleanArray(redoList)
redoList.clear()
undoList.push({ $case: 'crdt', operations: changes })
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { UndoRedoBuffer } from './undo-redo-buffer'
import { UndoRedo, UndoRedoFile } from '../undo-redo'

describe('UndoRedoBuffer', () => {
let buffer: UndoRedoBuffer

beforeEach(() => {
buffer = new UndoRedoBuffer(10, (val) => val.newValue, 10)
})

it('should push values to the buffer', () => {
const undoRedo: UndoRedo = { $case: 'file', operations: [] }
buffer.push(undoRedo)

expect(buffer.get()).toEqual([undoRedo])
})

it('should pop values from the buffer', () => {
const undoRedo1: UndoRedo = { $case: 'file', operations: [] }
const undoRedo2: UndoRedo = { $case: 'crdt', operations: [] }
buffer.push(undoRedo1)
buffer.push(undoRedo2)

expect(buffer.pop()).toEqual(undoRedo2)
expect(buffer.get()).toEqual([undoRedo1])
})

it('should handle pushing more than the maximum size', () => {
for (let i = 0; i < 15; i++) {
const undoRedo: UndoRedo = { $case: 'file', operations: [] }
buffer.push(undoRedo)
}

expect(buffer.get().length).toEqual(10)
})

it('should handle overwriting values when maximum entries size is reached', () => {
const acc: UndoRedo[] = []
for (let i = 0; i < 15; i++) {
const undoRedo: UndoRedo = {
$case: 'file',
operations: [{ path: `/file${i + 1}`, prevValue: null, newValue: new Uint8Array([i + 1]) }]
}
buffer.push(undoRedo)
acc.push(undoRedo)
}

const value = buffer.get()
expect(value.length).toEqual(10)
expect(value).toEqual(acc.slice(5))
})

it('should handle popping when the buffer is empty', () => {
expect(buffer.pop()).toBeUndefined()
})

it('should clear the buffer', () => {
const undoRedo: UndoRedo = { $case: 'file', operations: [] }
buffer.push(undoRedo)
buffer.clear()

expect(buffer.get()).toEqual([])
})

it('should correctly calculate the size of the buffer', () => {
const undoRedo1: UndoRedo = { $case: 'file', operations: [] }
const undoRedo2: UndoRedo = { $case: 'crdt', operations: [] }
buffer.push(undoRedo1)
buffer.push(undoRedo2)
buffer.pop()

expect(buffer.size).toEqual(1)
})

it('should handle pushing file operations based on memory size', () => {
const undoRedo: UndoRedoFile = {
$case: 'file',
operations: [
{ path: '/file1', prevValue: null, newValue: new Uint8Array([1, 2, 3]) },
{ path: '/file2', prevValue: null, newValue: new Uint8Array([4, 5, 6]) },
{ path: '/file3', prevValue: null, newValue: new Uint8Array([7, 8, 9]) }
]
}

buffer.push(undoRedo)

expect(buffer.get()).toEqual([undoRedo])
expect(buffer.inMemorySize).toEqual(9)
})

it('should handle popping file operations based on memory size', () => {
const undoRedo: UndoRedoFile = {
$case: 'file',
operations: [
{ path: '/file1', prevValue: null, newValue: new Uint8Array([1, 2, 3]) },
{ path: '/file2', prevValue: null, newValue: new Uint8Array([4, 5, 6]) },
{ path: '/file3', prevValue: null, newValue: new Uint8Array([7, 8, 9]) }
]
}

buffer.push(undoRedo)
buffer.pop()

expect(buffer.get()).toEqual([])
expect(buffer.inMemorySize).toEqual(0)
})

it('should throw when file operation exceeds max memory limit', () => {
const undoRedo: UndoRedoFile = {
$case: 'file',
operations: [
{ path: '/file1', prevValue: null, newValue: new Uint8Array([1, 2, 3]) },
{ path: '/file2', prevValue: null, newValue: new Uint8Array([4, 5, 6]) },
{ path: '/file3', prevValue: null, newValue: new Uint8Array([7, 8, 9]) },
{ path: '/file4', prevValue: null, newValue: new Uint8Array([10, 11, 12]) }
]
}

expect(() => buffer.push(undoRedo)).toThrow()
expect(buffer.inMemorySize).toEqual(0)
expect(buffer.size).toEqual(0)
expect(buffer.get()).toEqual([])
})

it('should handle popping file operations when memory exceeds the limit', () => {
const undoRedo: UndoRedoFile = {
$case: 'file',
operations: [
{ path: '/file1', prevValue: null, newValue: new Uint8Array([1, 2, 3]) },
{ path: '/file2', prevValue: null, newValue: new Uint8Array([4, 5, 6]) },
{ path: '/file3', prevValue: null, newValue: new Uint8Array([7, 8, 9, 10]) }
]
}
const undoRedo2: UndoRedoFile = {
$case: 'file',
operations: [{ path: '/file4', prevValue: null, newValue: new Uint8Array([11, 12, 13]) }]
}

buffer.push(undoRedo)
buffer.push(undoRedo2)

expect(buffer.inMemorySize).toEqual(3)
expect(buffer.get()).toEqual([undoRedo2])

const undoRedo3: UndoRedoFile = {
$case: 'file',
operations: [{ path: '/file5', prevValue: null, newValue: new Uint8Array([14, 15, 16]) }]
}

buffer.push(undoRedo3)

expect(buffer.get()).toEqual([undoRedo2, undoRedo3])
})
})
Loading

0 comments on commit e7ba7f7

Please sign in to comment.