fixed profiler so updates after pause; nes presets; resize windows; dialog for gist share

This commit is contained in:
Steven Hugg 2019-03-03 14:37:22 -06:00
parent ab1500ccb6
commit 733846af16
10 changed files with 184 additions and 119 deletions

View File

@ -431,3 +431,20 @@ div.markdown th {
height:100%;
overflow-y:auto;
}
div.profiler {
background-color: #333;
color: #ddd;
white-space: pre;
margin-top: 20px auto 0;
font-family: "Andale Mono", "Menlo", "Lucida Console", monospace;
font-size: 10pt;
}
span.profiler-lineno {
color: #fff;
}
span.profiler-local {
color: #999;
}
span.profiler-cident {
color: #99ffff;
}

View File

@ -82,8 +82,9 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
<li class="dropdown dropdown-submenu">
<a tabindex="-1" href="#">Share</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#" id="item_record_video">Record Video...</a></li>
<li><a class="dropdown-item" href="#" id="item_share_github">Share File on Github...</a></li>
<li><a class="dropdown-item" href="#" id="item_share_file">Share Playable Link...</a></li>
<li><a class="dropdown-item" href="#" id="item_record_video">Record Video...</a></li>
<li><a class="dropdown-item" href="#" id="item_export_cassette">Make Cassette Audio...</a></li>
</ul>
</li>
@ -178,7 +179,7 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
<span id="verilog_bar" style="display:none">
<span class="label"><span id="settle_label"></span> evals/clk</span>
</span>
<span class="dropdown" style="float:right">
<span class="logo-gradient hidden-xs hidden-sm">8bitworkshop</span>
&nbsp;
@ -292,6 +293,31 @@ if (window.location.host.endsWith('8bitworkshop.com')) {
</div>
</div>
</div>
<div id="embedGistModal" class="modal fade">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Share Code on Github</h3>
</div>
<div class="modal-body">
<p>If you have a Github account, you can share your code as a Github gist.</p>
<ul>
<li>Make sure the correct platform is selected.
<li>Go to <a href="https://gist.github.com/" target="_8bitgist">gist.github.com</a>
<li>Create a gist with your source code.
<li>Paste the final gist URL below.
</ul>
<textarea rows="2" cols="100" id="embedGistURL" class="cliptext"></textarea>
<p>The shareable URL will appear here:</p>
<textarea rows="2" cols="100" id="embedGistShareURL" class="cliptext"></textarea><br>
<button type="button" class="btn btn-primary" data-clipboard-target="#embedGistShareURL">Copy Shareable Link</button>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script src="jquery/jquery-3.3.1.min.js"></script>

View File

@ -9,7 +9,7 @@ word bcd_add(word a, word b) {
byte d = (a & 0xf) + (b & 0xf) + c;
c = 0;
while (d >= 10) {
c++;
++c;
d -= 10;
}
result |= d << shift;

View File

@ -2,9 +2,15 @@
#include "neslib.h"
#include "vrambuf.h"
#pragma bss-name(push,"ZEROPAGE")
#pragma data-name(push,"ZEROPAGE")
// index to end of buffer
byte updptr = 0;
#pragma bss-name(pop)
#pragma data-name(pop)
// add EOF marker to buffer
void cendbuf(void) {
updbuf[updptr] = NT_UPD_EOF;
@ -30,11 +36,12 @@ void cflushnow(void) {
// using horizontal increment
void putbytes(word addr, const char* str, byte len) {
if (updptr >= VBUFSIZE-4-len) cflushnow();
updbuf[updptr++] = (addr >> 8) ^ NT_UPD_HORZ;
updbuf[updptr++] = addr & 0xff;
updbuf[updptr++] = len;
updbuf[updptr] = (addr >> 8) ^ NT_UPD_HORZ;
updbuf[++updptr] = addr & 0xff;
updbuf[++updptr] = len;
while (len--) {
updbuf[updptr++] = *str++;
updbuf[++updptr] = *str++;
}
++updptr;
cendbuf();
}

View File

@ -157,6 +157,31 @@ export abstract class BasePlatform {
this.recorder.recordFrame(this.saveState());
}
}
startProfiling() : ProfilerOutput {
var frame = null;
var output = {frame:null};
var i = 0;
var lastsl = 9999;
var start = 0;
(this as any).runEval((c:CpuState) => {
var sl = (this as any).getRasterScanline();
if (sl != lastsl) {
if (frame) {
frame.lines.push({start:start,end:i-1});
}
if (sl < lastsl) {
output.frame = frame;
frame = {iptab:new Uint32Array(0x8000), lines:[]}; // TODO: const
i = 0;
}
start = i;
lastsl = sl;
}
frame.iptab[i++] = c.EPC || c.PC;
return false; // profile forever
});
return output;
}
}
export abstract class BaseDebugPlatform extends BasePlatform {
@ -208,29 +233,6 @@ export abstract class BaseDebugPlatform extends BasePlatform {
this.advance(novideo);
this.postFrame();
}
startProfiling() : ProfilerOutput {
var frame = null;
var output = {frame:null};
var i = 0;
var lastsl = 9999;
var start = 0;
(this as any).runEval((c:CpuState) => {
var sl = (this as any).getRasterScanline();
if (sl != lastsl) {
if (frame) frame.lines.push({start:start,end:i});
if (sl < lastsl) {
output.frame = frame;
frame = {iptab:new Uint32Array(14672), lines:[]};
i = 0;
}
start = i+1;
lastsl = sl;
}
frame.iptab[i++] = c.EPC || c.PC;
return false; // profile forever
});
return output;
}
}
////// 6502
@ -1028,6 +1030,7 @@ export abstract class BasicZ80ScanlinePlatform extends BaseZ80Platform {
abstract getKeyboardMap();
abstract startScanline(sl : number);
abstract drawScanline(sl : number);
getRasterScanline() { return this.currentScanline; }
constructor(mainElement : HTMLElement) {
super();

View File

@ -231,30 +231,6 @@ const _JSNESPlatform = function(mainElement) {
getRasterScanline() : number {
return nes.ppu.scanline;
}
startProfiling() : ProfilerOutput {
var frame0 = frameindex;
var frame = null;
var output = {frame:null};
var i = 0;
var lastsl = 9999;
var start = 0;
this.runEval((c) => {
var sl = this.getRasterScanline();
if (sl != lastsl) {
if (frame) frame.lines.push({start:start,end:i});
if (sl < lastsl) {
output.frame = frame;
frame = {iptab:new Uint32Array(14672), lines:[]};
i = 0;
}
start = i+1;
lastsl = sl;
}
frame.iptab[i++] = c.EPC || c.PC;
return false; //frameindex>frame0; // TODO
});
return output;
}
getCPUState() {
var c = nes.cpu.toJSON();

View File

@ -153,6 +153,7 @@ class VCSPlatform extends BasePlatform {
Javatari.room.speaker.mute();
}
isDebugging() : boolean {
// TODO: always true
return Javatari.room.console.onBreakpointHit != null;
}
clearDebug() {

View File

@ -354,28 +354,24 @@ function getCurrentEditorFilename() : string {
}
function _shareFileAsGist(e) {
loadScript("octokat.js/dist/octokat.js", () => {
if (current_output == null) { // TODO
alert("Please fix errors before sharing.");
return true;
}
var text = projectWindows.getCurrentText();
if (!text) return false;
var github = new exports['Octokat']();
var files = {};
files[getCurrentEditorFilename()] = {"content": text};
var gistdata = {
"description": '8bitworkshop.com {"platform":"' + platform_id + '"}',
"public": true,
"files": files
};
var gist = github.gists.create(gistdata).done(function(val) {
var url = "http://8bitworkshop.com/?gistkey=" + val.id;
window.prompt("Copy link to clipboard (Ctrl+C, Enter)", url);
}).fail(function(err) {
alert("Error sharing file: " + err.message);
var gisturl_ta = $("#embedGistURL");
var shareurl_ta = $("#embedGistShareURL");
loadClipboardLibrary();
gisturl_ta.change(() => {
var gisturl = gisturl_ta.val() + '';
var pos = gisturl.lastIndexOf('/');
if (pos >= 0) {
var gistkey = gisturl.substring(pos+1);
var embed = {
platform: platform_id,
gistkey: gistkey
};
var linkqs = $.param(embed);
var fulllink = get8bitworkshopLink(linkqs, 'redir.html');
shareurl_ta.val(fulllink);
}
});
});
$("#embedGistModal").modal('show');
}
function _shareEmbedLink(e) {
@ -387,10 +383,7 @@ function _shareEmbedLink(e) {
alert("Can't share a Verilog executable yet. (It's not actually a ROM...)");
return true;
}
loadScript('lib/clipboard.min.js', () => {
var ClipboardJS = exports['ClipboardJS'];
new ClipboardJS(".btn");
});
loadClipboardLibrary();
loadScript('lib/liblzg.js', () => {
// TODO: Module is bad var name (conflicts with MAME)
var lzgrom = compressLZG( window['Module'], Array.from(<Uint8Array>current_output) );
@ -402,11 +395,7 @@ function _shareEmbedLink(e) {
r: lzgb64
};
var linkqs = $.param(embed);
console.log(linkqs);
var loc = window.location;
var prefix = loc.pathname.replace('index.html','');
var protocol = (loc.host == '8bitworkshop.com') ? 'https:' : loc.protocol;
var fulllink = protocol+'//'+loc.host+prefix+'embed.html?' + linkqs;
var fulllink = get8bitworkshopLink(linkqs, 'embed.html');
var iframelink = '<iframe width=640 height=600 src="' + fulllink + '">';
$("#embedLinkTextarea").text(fulllink);
$("#embedIframeTextarea").text(iframelink);
@ -419,6 +408,22 @@ function _shareEmbedLink(e) {
return true;
}
function loadClipboardLibrary() {
loadScript('lib/clipboard.min.js', () => {
var ClipboardJS = exports['ClipboardJS'];
new ClipboardJS(".btn");
});
}
function get8bitworkshopLink(linkqs : string, fn : string) {
console.log(linkqs);
var loc = window.location;
var prefix = loc.pathname.replace('index.html','');
var protocol = (loc.host == '8bitworkshop.com') ? 'https:' : loc.protocol;
var fulllink = protocol+'//'+loc.host+prefix+fn+'?' + linkqs;
return fulllink;
}
function _downloadCassetteFile(e) {
if (current_output == null) { // TODO
alert("Please fix errors before exporting.");
@ -860,9 +865,15 @@ function getSymbolAtAddress(a : number) {
return '';
}
var debugTickPaused = false;
function updateDebugWindows() {
if (platform.isRunning()) {
projectWindows.tick();
debugTickPaused = false;
} else if (!debugTickPaused) { // final tick after pausing
projectWindows.tick();
debugTickPaused = true;
}
setTimeout(updateDebugWindows, 200);
}
@ -1094,6 +1105,7 @@ function setupDebugControls(){
$(".dropdown-menu").collapse({toggle: false});
$("#item_new_file").click(_createNewFile);
$("#item_upload_file").click(_uploadNewFile);
$("#item_share_github").click(_shareFileAsGist);
$("#item_share_file").click(_shareEmbedLink);
$("#item_reset_file").click(_revertFile);
$("#item_rename_file").click(_renameFile);
@ -1385,14 +1397,15 @@ function loadScript(scriptfn, onload) {
export function setupSplits() {
const splitName = 'workspace-split3-' + platform_id;
var sizes = [0, 50, 50];
if (platform_id != 'vcs') // TODO
sizes = [12, 44, 44];
var sizesStr = hasLocalStorage && localStorage.getItem(splitName);
if (sizesStr) {
try {
sizes = JSON.parse(sizesStr);
} catch (e) { console.log(e); }
}
var split;
split = Split(['#sidebar', '#workspace', '#emulator'], {
var split = Split(['#sidebar', '#workspace', '#emulator'], {
sizes: sizes,
minSize: [0, 250, 250],
onDrag: () => {
@ -1401,6 +1414,7 @@ export function setupSplits() {
onDragEnd: () => {
if (hasLocalStorage)
localStorage.setItem(splitName, JSON.stringify(split.getSizes()))
projectWindows.resize();
},
});
}

View File

@ -11,6 +11,7 @@ import { platform, platform_id, compparams, current_project, lastDebugState, pro
export interface ProjectView {
createDiv(parent:HTMLElement, text:string) : HTMLElement;
dispose?() : void;
refresh(moveCursor:boolean) : void;
tick?() : void;
getPath?() : string;
@ -24,6 +25,7 @@ export interface ProjectView {
markErrors?(errors:WorkerError[]) : void;
clearErrors?() : void;
setTimingResult?(result:CodeAnalyzer) : void;
recreateOnResize? : boolean;
};
declare var CodeMirror;
@ -36,6 +38,13 @@ function jumpToLine(ed, i:number) {
ed.scrollTo(null, t - middleHeight - 5);
}
function createTextSpan(text:string, className:string) : HTMLElement {
var span = document.createElement("span");
span.setAttribute("class", className);
span.appendChild(document.createTextNode(text));
return span;
}
// TODO: https://stackoverflow.com/questions/10463518/converting-em-to-px-in-javascript-and-getting-default-font-size
function getVisibleEditorLineHeight() : number{
return $("#booksMenuButton").first().height();
@ -119,9 +128,7 @@ export class SourceEditor implements ProjectView {
this.inspectWidget = null;
}
if (result) {
var infospan = document.createElement("span");
infospan.setAttribute("class", "tooltipinfoline");
infospan.appendChild(document.createTextNode(result));
var infospan = createTextSpan(result, "tooltipinfoline");
var line = this.editor.getCursor().line;
this.inspectWidget = this.editor.addLineWidget(line, infospan, {above:false});
}
@ -156,9 +163,7 @@ export class SourceEditor implements ProjectView {
}
addErrorLine(line:number, msg:string) {
var errspan = document.createElement("span");
errspan.setAttribute("class", "tooltiperrorline");
errspan.appendChild(document.createTextNode(msg));
var errspan = createTextSpan(msg, "tooltiperrorline");
this.errorwidgets.push(this.editor.addLineWidget(line, errspan));
}
@ -547,7 +552,7 @@ export class ListingView extends DisassemblerView implements ProjectView {
var asmtext = this.assemblyfile.text;
var disasmview = this.getDisasmView();
disasmview.setValue(asmtext);
var debugging = platform.isDebugging && platform.isDebugging();
var debugging = true; // TODO: platform.isDebugging && platform.isDebugging();
var findPC = debugging ? pc : -1;
if (findPC >= 0 && this.assemblyfile) {
var lineno = this.assemblyfile.findLineForOffset(findPC, 15);
@ -570,6 +575,7 @@ export class MemoryView implements ProjectView {
dumplines;
maindiv : HTMLElement;
static IGNORE_SYMS = {s__INITIALIZER:true, /* s__GSINIT:true, */ _color_prom:true};
recreateOnResize = true;
/*
read(addr:number) {
// TODO: b offset ?
@ -727,6 +733,7 @@ export class BinaryFileView implements ProjectView {
maindiv : HTMLElement;
path:string;
data:Uint8Array;
recreateOnResize = true;
constructor(path:string, data:Uint8Array) {
this.path = path;
@ -841,10 +848,11 @@ export class ProfileView implements ProjectView {
prof : ProfilerOutput;
maindiv : HTMLElement;
symcache : {};
recreateOnResize = true;
createDiv(parent : HTMLElement) {
var div = document.createElement('div');
div.setAttribute("class", "memdump");
div.setAttribute("class", "profiler");
parent.appendChild(div);
this.showMemoryWindow(parent, div);
return this.maindiv = div;
@ -857,9 +865,8 @@ export class ProfileView implements ProjectView {
itemHeight: getVisibleEditorLineHeight(),
totalRows: 262,
generatorFn: (row : number) => {
var s = this.getProfileLineAt(row);
var linediv = document.createElement("div");
linediv.appendChild(document.createTextNode(s));
this.addProfileLine(linediv, row);
return linediv;
}
});
@ -868,13 +875,13 @@ export class ProfileView implements ProjectView {
this.tick();
}
getProfileLineAt(row : number) : string {
var s = lpad(row+': ',5);
if (!this.prof) return s;
addProfileLine(div : HTMLElement, row : number) : void {
div.appendChild(createTextSpan(lpad(row+':',4), "profiler-lineno"));
if (!this.prof) return;
var f = this.prof.frame;
if (!f) return s;
if (!f) return;
var l = f.lines[row];
if (!l) return s;
if (!l) return;
var lastsym = '';
for (var i=l.start; i<=l.end; i++) {
var pc = f.iptab[i];
@ -884,11 +891,14 @@ export class ProfileView implements ProjectView {
this.symcache[pc] = sym;
}
if (sym != lastsym) {
s += sym + ' ';
var cls = "profiler";
if (sym.startsWith('_')) cls = "profiler-cident";
else if (sym.startsWith('@')) cls = "profiler-local";
else if (/^\d*[.]/.exec(sym)) cls = "profiler-local";
div.appendChild(createTextSpan(' '+sym, cls));
lastsym = sym;
}
}
return s;
}
refresh() {
@ -900,15 +910,17 @@ export class ProfileView implements ProjectView {
$(this.maindiv).find('[data-index]').each( (i,e) => {
var div = $(e);
var row = parseInt(div.attr('data-index'));
var oldtext = div.text();
var newtext = this.getProfileLineAt(row);
if (oldtext != newtext)
div.text(newtext);
div.empty();
this.addProfileLine(div[0], row);
});
}
// TODO: better way to keep it profiling? also, it clears the buffer
if (platform.isRunning()) {
// TODO: better way to keep it profiling single-frame? also, it clears the buffer
if (platform.isRunning() /* && !platform.isDebugging()*/ ) {
this.prof = platform.startProfiling();
}
}
dispose() : void {
platform.clearDebug();
}
}

View File

@ -10,7 +10,6 @@ type WindowCreateFunction = (id:string) => ProjectView;
export class ProjectWindows {
containerdiv:HTMLElement;
project:CodeProject;
id2window : {[id:string]:ProjectView} = {};
id2createfn : {[id:string]:WindowCreateFunction} = {};
id2div : {[id:string]:HTMLElement} = {};
@ -18,17 +17,17 @@ export class ProjectWindows {
activewnd : ProjectView;
activediv : HTMLElement;
lasterrors : WorkerError[];
constructor(containerdiv:HTMLElement, project:CodeProject) {
this.containerdiv = containerdiv;
this.project = project;
}
// TODO: delete windows ever?
setCreateFunc(id:string, createfn:WindowCreateFunction) : void {
this.id2createfn[id] = createfn;
}
createOrShow(id:string) : ProjectView {
var wnd = this.id2window[id];
if (!wnd) {
@ -42,6 +41,8 @@ export class ProjectWindows {
if (this.activewnd != wnd) {
if (this.activediv)
$(this.activediv).hide();
if (this.activewnd && this.activewnd.dispose)
this.activewnd.dispose();
this.activediv = div;
this.activewnd = wnd;
$(div).show();
@ -55,13 +56,13 @@ export class ProjectWindows {
put(id:string, window:ProjectView) : void {
this.id2window[id] = window;
}
refresh(moveCursor:boolean) : void {
// refresh current window
if (this.activewnd && this.activewnd.refresh)
this.activewnd.refresh(moveCursor);
}
tick() : void {
if (this.activewnd && this.activewnd.tick)
this.activewnd.tick();
@ -71,7 +72,7 @@ export class ProjectWindows {
this.lasterrors = errors;
this.refreshErrors();
}
refreshErrors() : void {
if (this.activewnd && this.activewnd.markErrors) {
if (this.lasterrors && this.lasterrors.length)
@ -80,9 +81,9 @@ export class ProjectWindows {
this.activewnd.clearErrors();
}
}
getActive() : ProjectView { return this.activewnd; }
getActiveID() : string { return this.activeid; }
getCurrentText() : string {
@ -91,5 +92,13 @@ export class ProjectWindows {
else
alert("Please switch to an editor window.");
}
};
resize() : void {
if (this.activeid && this.activewnd && this.activewnd.recreateOnResize) {
this.activewnd = null;
this.id2window[this.activeid] = null;
this.id2div[this.activeid] = null;
this.createOrShow(this.activeid);
}
}
};