Compare commits
2 commits
74743a1824
...
d9faea4c1c
Author | SHA1 | Date | |
---|---|---|---|
d9faea4c1c | |||
95708da8cc |
7 changed files with 124 additions and 57 deletions
1
msagent.js/.gitignore
vendored
Normal file
1
msagent.js/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
core/obj
|
|
@ -2,17 +2,25 @@
|
||||||
// Integer types from WASI-libc
|
// Integer types from WASI-libc
|
||||||
using usize = unsigned int;
|
using usize = unsigned int;
|
||||||
using u32 = unsigned int;
|
using u32 = unsigned int;
|
||||||
|
using u16 = unsigned short;
|
||||||
using u8 = unsigned char;
|
using u8 = unsigned char;
|
||||||
|
|
||||||
#define LOWORD(x) (x & 0xffff)
|
template<class T>
|
||||||
#define LOBYTE(x) (x & 0xff)
|
constexpr auto LowOrderShortOf(T item) {
|
||||||
|
return static_cast<u16>(item & 0xffff);
|
||||||
|
}
|
||||||
|
|
||||||
#define PUBLIC __attribute__((visibility("default"))) extern "C"
|
template<class T>
|
||||||
|
constexpr auto LowOrderByteOf(T item) {
|
||||||
|
return static_cast<u8>(item & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
PUBLIC usize agentDecompressWASM(const void* pSrcData, usize pSrcSize, void* pTrgData, usize pTrgSize) {
|
#define PUBLIC extern "C" __attribute__((visibility("default")))
|
||||||
const u8* lSrcPtr = (const u8*)pSrcData;
|
|
||||||
|
PUBLIC usize agentDecompressWASM(const u8* pSrcData, usize pSrcSize, u8* pTrgData, usize pTrgSize) {
|
||||||
|
const u8* lSrcPtr = pSrcData;
|
||||||
const u8* lSrcEnd = lSrcPtr + pSrcSize;
|
const u8* lSrcEnd = lSrcPtr + pSrcSize;
|
||||||
u8* lTrgPtr = (u8*)pTrgData;
|
u8* lTrgPtr = pTrgData;
|
||||||
u8* lTrgEnd = lTrgPtr + pTrgSize;
|
u8* lTrgEnd = lTrgPtr + pTrgSize;
|
||||||
u32 lSrcQuad;
|
u32 lSrcQuad;
|
||||||
u8 lTrgByte;
|
u8 lTrgByte;
|
||||||
|
@ -38,15 +46,15 @@ PUBLIC usize agentDecompressWASM(const void* pSrcData, usize pSrcSize, void* pTr
|
||||||
lSrcPtr += 5;
|
lSrcPtr += 5;
|
||||||
|
|
||||||
while((lSrcPtr < lSrcEnd) && (lTrgPtr < lTrgEnd)) {
|
while((lSrcPtr < lSrcEnd) && (lTrgPtr < lTrgEnd)) {
|
||||||
lSrcQuad = *(const u32*)(lSrcPtr - sizeof(u32));
|
lSrcQuad = *reinterpret_cast<const u32*>(lSrcPtr - sizeof(u32));
|
||||||
|
|
||||||
if(lSrcQuad & (1 << LOWORD(lBitCount))) {
|
if(lSrcQuad & (1 << LowOrderShortOf(lBitCount))) {
|
||||||
lSrcOffset = 1;
|
lSrcOffset = 1;
|
||||||
|
|
||||||
if(lSrcQuad & (1 << LOWORD(lBitCount + 1))) {
|
if(lSrcQuad & (1 << LowOrderShortOf(lBitCount + 1))) {
|
||||||
if(lSrcQuad & (1 << LOWORD(lBitCount + 2))) {
|
if(lSrcQuad & (1 << LowOrderShortOf(lBitCount + 2))) {
|
||||||
if(lSrcQuad & (1 << LOWORD(lBitCount + 3))) {
|
if(lSrcQuad & (1 << LowOrderShortOf(lBitCount + 3))) {
|
||||||
lSrcQuad >>= LOWORD(lBitCount + 4);
|
lSrcQuad >>= LowOrderShortOf(lBitCount + 4);
|
||||||
lSrcQuad &= 0x000FFFFF;
|
lSrcQuad &= 0x000FFFFF;
|
||||||
if(lSrcQuad == 0x000FFFFF) {
|
if(lSrcQuad == 0x000FFFFF) {
|
||||||
break;
|
break;
|
||||||
|
@ -56,19 +64,19 @@ PUBLIC usize agentDecompressWASM(const void* pSrcData, usize pSrcSize, void* pTr
|
||||||
|
|
||||||
lSrcOffset = 2;
|
lSrcOffset = 2;
|
||||||
} else {
|
} else {
|
||||||
lSrcQuad >>= LOWORD(lBitCount + 4);
|
lSrcQuad >>= LowOrderShortOf(lBitCount + 4);
|
||||||
lSrcQuad &= 0x00000FFF;
|
lSrcQuad &= 0x00000FFF;
|
||||||
lSrcQuad += 577;
|
lSrcQuad += 577;
|
||||||
lBitCount += 16;
|
lBitCount += 16;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lSrcQuad >>= LOWORD(lBitCount + 3);
|
lSrcQuad >>= LowOrderShortOf(lBitCount + 3);
|
||||||
lSrcQuad &= 0x000001FF;
|
lSrcQuad &= 0x000001FF;
|
||||||
lSrcQuad += 65;
|
lSrcQuad += 65;
|
||||||
lBitCount += 12;
|
lBitCount += 12;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lSrcQuad >>= LOWORD(lBitCount + 2);
|
lSrcQuad >>= LowOrderShortOf(lBitCount + 2);
|
||||||
lSrcQuad &= 0x0000003F;
|
lSrcQuad &= 0x0000003F;
|
||||||
lSrcQuad += 1;
|
lSrcQuad += 1;
|
||||||
lBitCount += 8;
|
lBitCount += 8;
|
||||||
|
@ -76,16 +84,16 @@ PUBLIC usize agentDecompressWASM(const void* pSrcData, usize pSrcSize, void* pTr
|
||||||
|
|
||||||
lSrcPtr += (lBitCount / 8);
|
lSrcPtr += (lBitCount / 8);
|
||||||
lBitCount &= 7;
|
lBitCount &= 7;
|
||||||
lRunLgth = *(const u32*)(lSrcPtr - sizeof(u32));
|
lRunLgth = *reinterpret_cast<const u32*>(lSrcPtr - sizeof(u32));
|
||||||
lRunCount = 0;
|
lRunCount = 0;
|
||||||
while(lRunLgth & (1 << LOWORD(lBitCount + lRunCount))) {
|
while(lRunLgth & (1 << LowOrderShortOf(lBitCount + lRunCount))) {
|
||||||
lRunCount++;
|
lRunCount++;
|
||||||
if(lRunCount > 11) {
|
if(lRunCount > 11) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lRunLgth >>= LOWORD(lBitCount + lRunCount + 1);
|
lRunLgth >>= LowOrderShortOf(lBitCount + lRunCount + 1);
|
||||||
lRunLgth &= (1 << lRunCount) - 1;
|
lRunLgth &= (1 << lRunCount) - 1;
|
||||||
lRunLgth += 1 << lRunCount;
|
lRunLgth += 1 << lRunCount;
|
||||||
lRunLgth += lSrcOffset;
|
lRunLgth += lSrcOffset;
|
||||||
|
@ -103,10 +111,10 @@ PUBLIC usize agentDecompressWASM(const void* pSrcData, usize pSrcSize, void* pTr
|
||||||
lRunLgth--;
|
lRunLgth--;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lSrcQuad >>= LOWORD(lBitCount + 1);
|
lSrcQuad >>= LowOrderShortOf(lBitCount + 1);
|
||||||
lBitCount += 9;
|
lBitCount += 9;
|
||||||
|
|
||||||
lTrgByte = LOBYTE(lSrcQuad);
|
lTrgByte = LowOrderByteOf(lSrcQuad);
|
||||||
*(lTrgPtr++) = lTrgByte;
|
*(lTrgPtr++) = lTrgByte;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,5 +122,5 @@ PUBLIC usize agentDecompressWASM(const void* pSrcData, usize pSrcSize, void* pTr
|
||||||
lBitCount &= 7;
|
lBitCount &= 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (usize)(lTrgPtr - (u8*)pTrgData);
|
return static_cast<usize>(lTrgPtr - pTrgData);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,8 +28,7 @@ function compressWASMGetMemory(): WebAssembly.Memory {
|
||||||
// Decompress Agent compressed data. This compression algorithm sucks.
|
// Decompress Agent compressed data. This compression algorithm sucks.
|
||||||
// [dest] is to be preallocated to the decompressed data size.
|
// [dest] is to be preallocated to the decompressed data size.
|
||||||
export function compressDecompress(src: Uint8Array, dest: Uint8Array) {
|
export function compressDecompress(src: Uint8Array, dest: Uint8Array) {
|
||||||
// Grow the WASM heap if needed. Funnily enough, this code is never hit in most
|
// Grow the WASM heap if needed.
|
||||||
// ACSes, so IDK if it's even needed
|
|
||||||
compressWasm.growHeapTo(src.length + dest.length);
|
compressWasm.growHeapTo(src.length + dest.length);
|
||||||
|
|
||||||
let memory = compressWASMGetMemory();
|
let memory = compressWASMGetMemory();
|
||||||
|
@ -41,7 +40,7 @@ export function compressDecompress(src: Uint8Array, dest: Uint8Array) {
|
||||||
// Call the WASM compression routine
|
// Call the WASM compression routine
|
||||||
let nrBytesDecompressed = compressWasmGetExports().agentDecompressWASM(0, src.length, src.length, dest.length);
|
let nrBytesDecompressed = compressWasmGetExports().agentDecompressWASM(0, src.length, src.length, dest.length);
|
||||||
|
|
||||||
if (nrBytesDecompressed != dest.length) throw new Error(`decompression failed: ${nrBytesDecompressed} != ${dest.length}`);
|
if (nrBytesDecompressed != dest.length) throw new Error(`Decompression failed: Output ${nrBytesDecompressed} != expected ${dest.length}`);
|
||||||
|
|
||||||
// The uncompressed data is located at memory[src.length..dest.length].
|
// The uncompressed data is located at memory[src.length..dest.length].
|
||||||
// Copy it into the destination buffer.
|
// Copy it into the destination buffer.
|
||||||
|
|
|
@ -10,44 +10,85 @@ function randint(min: number, max: number) {
|
||||||
return Math.floor(Math.random() * (max - min) + min);
|
return Math.floor(Math.random() * (max - min) + min);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum AnimState {
|
||||||
|
Idle = 0,
|
||||||
|
Cancel = 1,
|
||||||
|
Playing = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// an externally resolveable promise
|
||||||
|
class ExternallyResolveablePromise<T> {
|
||||||
|
private rescb: (t: T) => void;
|
||||||
|
private rejectcb: (e: Error) => void;
|
||||||
|
public promise: Promise<T>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
let rescb;
|
||||||
|
let rejectcb;
|
||||||
|
|
||||||
|
this.promise = new Promise<T>((res, rej) => {
|
||||||
|
rescb = res;
|
||||||
|
rejectcb = rej;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.rescb = rescb!;
|
||||||
|
this.rejectcb = rejectcb!;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(t: T) {
|
||||||
|
this.rescb(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(e: Error) {
|
||||||
|
this.rejectcb(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
raw() {
|
||||||
|
return this.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// animation state (used during animation playback)
|
// animation state (used during animation playback)
|
||||||
class AgentAnimationState {
|
class AgentAnimationState {
|
||||||
char: Agent;
|
char: Agent;
|
||||||
anim: AcsAnimation;
|
anim: AcsAnimation;
|
||||||
private cancelled: boolean = false;
|
|
||||||
|
|
||||||
finishCallback: () => void;
|
private animState: AnimState;
|
||||||
|
private finishPromise = new ExternallyResolveablePromise<void>();
|
||||||
|
|
||||||
frameIndex = 0;
|
frameIndex = 0;
|
||||||
|
|
||||||
interval = 0;
|
interval = 0;
|
||||||
|
|
||||||
constructor(char: Agent, anim: AcsAnimation, finishCallback: () => void) {
|
constructor(char: Agent, anim: AcsAnimation) {
|
||||||
this.char = char;
|
this.char = char;
|
||||||
this.anim = anim;
|
this.anim = anim;
|
||||||
this.finishCallback = finishCallback;
|
this.animState = AnimState.Idle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// start playing the animation
|
// start playing the animation
|
||||||
play() {
|
async play() {
|
||||||
|
this.animState = AnimState.Playing;
|
||||||
this.nextFrame();
|
this.nextFrame();
|
||||||
|
await this.finishPromise.raw();
|
||||||
|
|
||||||
|
this.animState = AnimState.Idle;
|
||||||
|
clearInterval(this.interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel() {
|
async cancel() {
|
||||||
this.cancelled = true;
|
this.animState = AnimState.Cancel;
|
||||||
clearTimeout(this.interval);
|
await this.finishPromise.raw();
|
||||||
}
|
}
|
||||||
|
|
||||||
nextFrame() {
|
nextFrame() {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (this.cancelled) return;
|
|
||||||
|
|
||||||
// Handle animation branching, if it is required
|
// Handle animation branching, if it is required
|
||||||
let bi = this.anim.frameInfo[this.frameIndex].branchInfo;
|
let bi = this.anim.frameInfo[this.frameIndex].branchInfo;
|
||||||
if (bi.length != 0) {
|
if (bi.length != 0) {
|
||||||
let biCopy = [...bi];
|
let biCopy = [...bi];
|
||||||
|
|
||||||
// This happens more often then you'd think, but this basically handles
|
// This happens more often then you'd think, but this basically handles
|
||||||
// a branch that will always be taken.
|
// a branch that will always be taken.
|
||||||
//
|
//
|
||||||
// This is often used for looping from my understanding?
|
// This is often used for looping from my understanding?
|
||||||
if (bi.length == 1 && bi[0].branchFrameProbability == 100) {
|
if (bi.length == 1 && bi[0].branchFrameProbability == 100) {
|
||||||
|
@ -61,7 +102,7 @@ class AgentAnimationState {
|
||||||
// Handles the off chance that there is a branch info list that sums less than 100%.
|
// Handles the off chance that there is a branch info list that sums less than 100%.
|
||||||
// (Office Logo 'Idle3', Victor has a couple, ...)
|
// (Office Logo 'Idle3', Victor has a couple, ...)
|
||||||
//
|
//
|
||||||
// I'm not entirely sure the correct action in this case but I just
|
// I'm not entirely sure the correct action in this case but I just
|
||||||
// have this do nothing.
|
// have this do nothing.
|
||||||
if (totalProbability != 100) {
|
if (totalProbability != 100) {
|
||||||
let nothingBranchItem = new AcsBranchInfo();
|
let nothingBranchItem = new AcsBranchInfo();
|
||||||
|
@ -85,10 +126,26 @@ class AgentAnimationState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.char.drawAnimationFrame(this.anim.frameInfo[this.frameIndex++]);
|
// Actually draw the requested frame
|
||||||
|
let fi = this.anim.frameInfo[this.frameIndex];
|
||||||
|
|
||||||
|
this.char.drawAnimationFrame(fi);
|
||||||
|
|
||||||
|
// If we've requested to cancel the animation and we're in a branch loop,
|
||||||
|
// then this is how we get out of it.
|
||||||
|
if (this.animState == AnimState.Cancel) {
|
||||||
|
if (fi.branchExitFrameIndex != -2) {
|
||||||
|
this.frameIndex = fi.branchExitFrameIndex;
|
||||||
|
} else {
|
||||||
|
this.finishPromise.resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.frameIndex += 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.frameIndex >= this.anim.frameInfo.length) {
|
if (this.frameIndex >= this.anim.frameInfo.length) {
|
||||||
this.finishCallback();
|
this.finishPromise.resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -366,30 +423,32 @@ export class Agent {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add promise versions later.
|
// add promise versions later.
|
||||||
playAnimation(index: number, finishCallback: () => void) {
|
async playAnimation(index: number): Promise<void> {
|
||||||
if (this.animState != null) {
|
if (this.animState != null) {
|
||||||
this.animState.cancel();
|
await this.animState.cancel();
|
||||||
this.animState = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let animInfo = this.data.animInfo[index];
|
let animInfo = this.data.animInfo[index];
|
||||||
|
|
||||||
// Create and start the animation state
|
// Create and start the animation state
|
||||||
this.animState = new AgentAnimationState(this, animInfo.animationData, () => {
|
this.animState = new AgentAnimationState(this, animInfo.animationData);
|
||||||
this.animState = null;
|
await this.animState.play();
|
||||||
finishCallback();
|
this.animState = null;
|
||||||
});
|
|
||||||
this.animState.play();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
playAnimationByName(name: string, finishCallback: () => void) {
|
async playAnimationByName(name: string): Promise<void> {
|
||||||
let index = this.data.animInfo.findIndex((n) => n.name == name);
|
let index = this.data.animInfo.findIndex((n) => n.name == name);
|
||||||
if (index !== -1) this.playAnimation(index, finishCallback);
|
if (index !== -1) return this.playAnimation(index);
|
||||||
|
//throw new Error(`Unknown animation \"${name}\"`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
playAnimationByNamePromise(name: string): Promise<void> {
|
async exitAnimation(): Promise<void> {
|
||||||
return new Promise((res, rej) => {
|
if (this.animState != null) {
|
||||||
this.playAnimationByName(name, () => res());
|
await this.animState.cancel();
|
||||||
});
|
this.animState = null;
|
||||||
|
}
|
||||||
|
// (After this we need to play the restpose animation or whatever exit is applicable)
|
||||||
}
|
}
|
||||||
|
|
||||||
setUsername(username: string, color: string) {
|
setUsername(username: string, color: string) {
|
||||||
|
|
|
@ -397,7 +397,7 @@ export class MSAgentClient {
|
||||||
let animMsg = msg as MSAgentAnimationMessage;
|
let animMsg = msg as MSAgentAnimationMessage;
|
||||||
let user = this.users.find((u) => u.username === animMsg.data.username);
|
let user = this.users.find((u) => u.username === animMsg.data.username);
|
||||||
if (!user || user.muted) return;
|
if (!user || user.muted) return;
|
||||||
await user.agent.playAnimationByNamePromise(animMsg.data.anim);
|
await user.agent.playAnimationByName(animMsg.data.anim);
|
||||||
await user.doAnim('rest');
|
await user.doAnim('rest');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ input.addEventListener('change', async () => {
|
||||||
agent.addToDom(mount);
|
agent.addToDom(mount);
|
||||||
|
|
||||||
agent.show();
|
agent.show();
|
||||||
await agent.playAnimationByNamePromise("Show");
|
await agent.playAnimationByName("Show");
|
||||||
console.log('Agent created');
|
console.log('Agent created');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ form.addEventListener('submit', async (e) => {
|
||||||
agent.addToDom(document.body);
|
agent.addToDom(document.body);
|
||||||
|
|
||||||
agent.show();
|
agent.show();
|
||||||
await agent.playAnimationByNamePromise("Show");
|
await agent.playAnimationByName("Show");
|
||||||
|
|
||||||
console.log(`Loaded agent from ${url}`);
|
console.log(`Loaded agent from ${url}`);
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,7 +20,7 @@ export class User {
|
||||||
async doAnim(action: string) {
|
async doAnim(action: string) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
for (let anim of this.animations[action]) {
|
for (let anim of this.animations[action]) {
|
||||||
await this.agent.playAnimationByNamePromise(anim);
|
await this.agent.playAnimationByName(anim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue