From 5967a7185a739f217d3928a8497f343eec5ebc0c Mon Sep 17 00:00:00 2001 From: zenith391 Date: Sun, 21 Apr 2019 13:52:32 +0200 Subject: [PATCH 01/16] Filesystem (optional) size limitation Filesystem size can finally be limited, however it is optional for people not wanting a behavior closed to OC. Added drive's component from a fork of OCEmu. Also added a check in it to create the directory if not existing. Added fork information in README.md --- README.md | 3 + src/component/drive.lua | 171 +++++++++++++++++++++++++++++++++++ src/component/filesystem.lua | 42 ++++++++- 3 files changed, 211 insertions(+), 5 deletions(-) create mode 100644 src/component/drive.lua diff --git a/README.md b/README.md index 917f6eb..a6e217a 100644 --- a/README.md +++ b/README.md @@ -88,3 +88,6 @@ If you want to use a custom path (for example, for running multiple machines wit cd src lua boot.lua /path/to/my/emulated/machine_a ``` + +## The fork on itself +The fork on itself is a update of the, now to seem, not working on project by gamax92. In this fork i'm going to try to fix all known bugs and to verify and add existing pull requests, and also treat issues on my own. diff --git a/src/component/drive.lua b/src/component/drive.lua new file mode 100644 index 0000000..18b0034 --- /dev/null +++ b/src/component/drive.lua @@ -0,0 +1,171 @@ +local address, _, directory, label, tier = ... + +compCheckArg(1,directory,"string","nil") +compCheckArg(2,label,"string","nil") +compCheckArg(3,tier,"number") + +if directory == nil then + directory = elsa.filesystem.getSaveDirectory() +end + +if not elsa.filesystem.exists(directory) then + elsa.filesystem.createDirectory(directory) +end + +local savePath = directory .. "/" .. address .. ".bin" +local platterCount = tier == 3 and 6 or tier == 2 and 4 or 2 +local capacity = (tier == 3 and 4096 or tier == 2 and 2048 or 1024) * 1024 +local sectorSize = 512 +local sectorCount = capacity / sectorSize +local sectorsPerPlatter = sectorCount / platterCount +local headPos = 0 +local data + +local mai = {} +local obj = {} + +local readSectorCosts = {1.0 / 10, 1.0 / 20, 1.0 / 30, 1.0 / 40, 1.0 / 50, 1.0 / 60} +local writeSectorCosts = {1.0 / 5, 1.0 / 10, 1.0 / 15, 1.0 / 20, 1.0 / 25, 1.0 / 30} +local readByteCosts = {1.0 / 48, 1.0 / 64, 1.0 / 80, 1.0 / 96, 1.0 / 112, 1.0 / 128} +local writeByteCosts = {1.0 / 24, 1.0 / 32, 1.0 / 40, 1.0 / 48, 1.0 / 56, 1.0 / 64} + +local function save() + local file = elsa.filesystem.newFile(savePath, "w") + file:write(data) + file:close() +end + +local function load() + if not elsa.filesystem.exists(savePath) then + data = string.rep(string.char(0), capacity) + return + end + local file = elsa.filesystem.newFile(savePath, "r") + data = file:read("*a") + file:close() +end + +load() + +local function validateSector(sector) + if sector < 0 or sector >= sectorCount then + error("invalid offset, not in a usable sector") + end + return sector +end + +local function offsetSector(sector) + return sector / sectorSize +end + +local function sectorOffset(sector) + return sector * sectorSize +end + +local function checkSector(sector) + return validateSector(sector - 1) +end + +local function checkSectorR(sector) + return validateSector(offsetSector(sector)) +end + +local function sectorToHeadPos(sector) + return sector % sectorsPerPlatter +end + +local function moveToSector(sector) + local newHeadPos = sectorToHeadPos(sector) + if headPos ~= newHeadPos then + headPos = newHeadPos + end + return sector +end + +mai.getLabel = {direct = true, doc = "function():string -- Get the current label of the drive."} +function obj.getLabel() + cprint("drive.getLabel") + return label +end + +mai.setLabel = {doc = "function(value:string):string -- Sets the label of the drive. Returns the new value, which may be truncated."} +function obj.setLabel(value) + cprint("drive.setLabel", value) + compCheckArg(1, value, "string") + value = value:sub(1,16) + if label ~= value then + label = value + for _, v in pairs(settings.components) do + if v[1] == "drive" and v[2] == address then + v[5] = label + end + end + config.save() + end +end + +mai.getCapacity = {direct = true, doc = "function():number -- Returns the total capacity of the drive, in bytes."} +function obj.getCapacity() + cprint("drive.getCapacity") + return capacity +end + +mai.getSectorSize = {direct = true, doc = "function():number -- Returns the size of a single sector on the drive, in bytes."} +function obj.getSectorSize() + cprint("drive.getSectorSize") + return sectorSize +end + +mai.getPlatterCount = {direct = true, doc = "function():number -- Returns the number of platters in the drive."} +function obj.getPlatterCount() + cprint("drive.getPlatterCount") + return platterCount +end + +mai.readSector = {direct = true, doc = "function(sector:number):string -- Read the current contents of the specified sector."} +function obj.readSector(sector) + cprint("drive.readSector", sector) + compCheckArg(1, sector, "number") + if not machine.consumeCallBudget(readSectorCosts[speed]) then return end + local s = moveToSector(checkSector(sector)) + return string.sub(data, sectorOffset(s), sectorOffset(s) + sectorSize) +end + +mai.writeSector = {direct = true, doc = "function(sector:number, value:string) -- Write the specified contents to the specified sector."} +function obj.writeSector(sector, value) + cprint("drive.writeSector", sector, value) + compCheckArg(1, sector, "number") + compCheckArg(1, value, "string") + if not machine.consumeCallBudget(writeSectorCosts[speed]) then return end + local s = moveToSector(checkSector(sector)) + local a = string.sub(data, 1, sectorOffset(s)) + local b = string.sub(data, sectorOffset(s) + math.min(sectorSize, #value) + 1, capacity) + data = a .. value .. b + save() +end + +mai.readByte = {direct = true, doc = "function(offset:number):number -- Read a single byte at the specified offset."} +function obj.readByte(offset) + cprint("drive.readByte", offset) + compCheckArg(1, offset, "number") + if not machine.consumeCallBudget(readByteCosts[speed]) then return end + local s = moveToSector(checkSectorR(offset)) + return string.byte(string.sub(data, sectorOffset(s), sectorOffset(s))) +end + +mai.writeByte = {direct = true, doc = "function(offset:number, value:number) -- Write a single byte to the specified offset."} +function obj.writeByte(offset, value) + cprint("drive.writeByte", offset, value) + compCheckArg(1, offset, "number") + compCheckArg(1, value, "number") + if not machine.consumeCallBudget(writeByteCosts[speed]) then return end + local s = moveToSector(checkSectorR(offset)) + local a = string.sub(data, 1, sectorOffset(s)) + local b = string.sub(data, sectorOffset(s) + 2, capacity) + data = a .. string.char(value) .. b + save() +end + + +return obj, nil, mai + diff --git a/src/component/filesystem.lua b/src/component/filesystem.lua index 85a6b64..ddf01db 100644 --- a/src/component/filesystem.lua +++ b/src/component/filesystem.lua @@ -1,4 +1,6 @@ -local address, _, directory, label, readonly, speed = ... +local address, _, directory, label, readonly, speed, size = ... +size = size or math.huge +local usedSize = 0 compCheckArg(1,directory,"string","nil") compCheckArg(2,label,"string","nil") compCheckArg(3,readonly,"boolean") @@ -49,6 +51,33 @@ local writeCosts = {1/1, 1/2, 1/3, 1/4, 1/5, 1/6} local mai = {} local obj = {} +local function getAllFiles(dirPath, tab) + tab = tab or {} + dirPath = cleanPath(dirPath) + local items = elsa.filesystem.getDirectoryItems(directory .. dirPath) + for k, v in pairs(items) do + if elsa.filesystem.isDirectory(directory .. dirPath .. "/" .. v) then + getAllFiles(dirPath .. "/" .. v, tab) + else + table.insert(tab, directory .. dirPath .. "/" .. v) + end + end + return tab +end + +local function calcUsedSpace() + local files = getAllFiles("/") + usedSize = 0 + for k, v in pairs(files) do + local path = v + usedSize = usedSize + 512 -- default OC emulation of "file info" + usedSize = usedSize + elsa.filesystem.getSize(v) + end + return usedSize +end + +calcUsedSpace() -- get used space + mai.read = {direct = true, limit = 15, doc = "function(handle:number, count:number):string or nil -- Reads up to the specified amount of data from an open file descriptor with the specified handle. Returns nil when EOF is reached."} function obj.read(handle, count) --TODO @@ -79,9 +108,8 @@ end mai.spaceUsed = {direct = true, doc = "function():number -- The currently used capacity of the file system, in bytes."} function obj.spaceUsed() - --STUB cprint("filesystem.spaceUsed") - return 0 + return usedSpace end mai.rename = {doc = "function(from:string, to:string):boolean -- Renames/moves an object from the first specified absolute path in the file system to the second."} @@ -131,6 +159,11 @@ function obj.write(handle, value) if handles[handle] == nil or (handles[handle][2] ~= "w" and handles[handle][2] ~= "a") then return nil, "bad file descriptor" end + local len = value:len() + if usedSize + len > size then + return nil, "not enough space" -- todo use OC error message + end + usedSize = usedSize + len -- if sucedded, add to used space. handles[handle][1]:write(value) return true end @@ -176,9 +209,8 @@ end mai.spaceTotal = {direct = true, doc = "function():number -- The overall capacity of the file system, in bytes."} function obj.spaceTotal() - --STUB cprint("filesystem.spaceTotal") - return math.huge + return size end mai.getLabel = {direct = true, doc = "function():string -- Get the current label of the file system."} From 5f08340c1901a218df9d29048f42d8d77beabc47 Mon Sep 17 00:00:00 2001 From: zenith391 Date: Sun, 21 Apr 2019 13:55:51 +0200 Subject: [PATCH 02/16] Fixed filesystem.lua --- src/component/filesystem.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/component/filesystem.lua b/src/component/filesystem.lua index ddf01db..892d81a 100644 --- a/src/component/filesystem.lua +++ b/src/component/filesystem.lua @@ -53,7 +53,6 @@ local obj = {} local function getAllFiles(dirPath, tab) tab = tab or {} - dirPath = cleanPath(dirPath) local items = elsa.filesystem.getDirectoryItems(directory .. dirPath) for k, v in pairs(items) do if elsa.filesystem.isDirectory(directory .. dirPath .. "/" .. v) then @@ -109,7 +108,7 @@ end mai.spaceUsed = {direct = true, doc = "function():number -- The currently used capacity of the file system, in bytes."} function obj.spaceUsed() cprint("filesystem.spaceUsed") - return usedSpace + return usedSize end mai.rename = {doc = "function(from:string, to:string):boolean -- Renames/moves an object from the first specified absolute path in the file system to the second."} From e505f964aec390497560e7a58af14fae97784e41 Mon Sep 17 00:00:00 2001 From: zenith391 Date: Fri, 31 May 2019 21:42:18 +0200 Subject: [PATCH 03/16] Finally added clipboard paste using middle mouse button --- src/component/screen_sdl2.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/component/screen_sdl2.lua b/src/component/screen_sdl2.lua index 2799161..99f31f8 100644 --- a/src/component/screen_sdl2.lua +++ b/src/component/screen_sdl2.lua @@ -15,6 +15,9 @@ local scrrfc, scrrbc = scrfgc, scrbgc local palcol = {} local precise = false +local obj = {type="screen"} + + t3pal = {} for i = 0,15 do t3pal[i] = (i+1)*0x0F0F0F @@ -84,6 +87,11 @@ function elsa.mousebuttonup(event) if bttndown and buttons[mbevent.button] then table.insert(machine.signals,{"drop",address,lx,ly,buttons[mbevent.button]}) bttndown = nil + else + if elsa.SDL.hasClipboardText() then + local text = ffi.string(elsa.SDL.getClipboardText()) + table.insert(machine.signals, {"clipboard", obj.getKeyboards()[1], text}) + end end end @@ -328,7 +336,6 @@ local touchinvert = false -- screen component local mai = {} -local obj = {type="screen"} mai.isTouchModeInverted = {doc = "function():boolean -- Whether touch mode is inverted (sneak-activate opens GUI, instead of normal activate)."} function obj.isTouchModeInverted() From 3945edacf7a91b913aa85d79be0dae1a43af397d Mon Sep 17 00:00:00 2001 From: zenith391 Date: Fri, 31 May 2019 22:03:42 +0200 Subject: [PATCH 04/16] Some progress --- README.md | 7 +++++++ src/config.lua | 1 + src/main.lua | 4 ++++ src/settings.lua | 1 + 4 files changed, 13 insertions(+) diff --git a/README.md b/README.md index a6e217a..bf64d1e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ OCEmu - OpenComputers Emulator ============================== +# Features added compared to original +75% - (Optional) Filesystem size limitation +TODO- Close on program termination +TODO- Debugger mode. Useful for coding OSes +TODO- Profiler +TODO- Automatic unit tests + Installation ------------ diff --git a/src/config.lua b/src/config.lua index 545df56..f1a29a0 100644 --- a/src/config.lua +++ b/src/config.lua @@ -18,6 +18,7 @@ local comments = { ["emulator.debug"]="Whether to enable the emulator's extremely verbose logging.", ["emulator.fast"]="Whether to choose performance over timing-accuracy.", ["emulator.vague"]="Whether to return vague error messages like OpenComputers.", +["emulator.profiler"]="Whether to enable real-time profiler or not.", ["filesystem.maxReadBuffer"]="The maximum block size that can be read in one 'read' call on a file system. This is used to limit the amount of memory a call from a user program can cause to be allocated on the host side: when 'read' is, called a byte array with the specified size has to be allocated. So if this weren't limited, a Lua program could trigger massive memory allocations regardless of the amount of RAM installed in the computer it runs on. As a side effect this pretty much determines the read performance of file systems.", ["internet.enableHttp"]="Whether to allow HTTP requests via internet cards. When enabled, the `request` method on internet card components becomes available.", ["internet.enableTcp"]="Whether to allow TCP connections via internet cards. When enabled, the `connect` method on internet card components becomes available.", diff --git a/src/main.lua b/src/main.lua index 16f2372..79f7501 100644 --- a/src/main.lua +++ b/src/main.lua @@ -96,6 +96,10 @@ if settings.components == nil then config.set("emulator.components",settings.components) end +if settings.profiler then + +end + local maxCallBudget = (1.5 + 1.5 + 1.5) / 3 -- T3 CPU and 2 T3+ memory machine = { diff --git a/src/settings.lua b/src/settings.lua index ef7a791..5df3c8e 100644 --- a/src/settings.lua +++ b/src/settings.lua @@ -21,6 +21,7 @@ settings = { maxNetworkPacketSize = config.get("misc.maxNetworkPacketSize",8192), maxWirelessRange = config.get("misc.maxWirelessRange",400), + profiler = config.get("emulator.profiler",false), } if settings.monochromeColor == nil then From f56aa0384e7cc38b169a768154ce908714f265a4 Mon Sep 17 00:00:00 2001 From: zenith391 Date: Sat, 1 Jun 2019 12:05:26 +0200 Subject: [PATCH 05/16] New window for profiler --- src/component/ocemu.lua | 6 ++++++ src/main.lua | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/component/ocemu.lua b/src/component/ocemu.lua index 31457fb..0a5348d 100644 --- a/src/component/ocemu.lua +++ b/src/component/ocemu.lua @@ -4,6 +4,7 @@ component.connect("filesystem", gen_uuid(), -1, "customlua/ocemu", "ocemu", true, 5) local components = settings.components +local profiler = settings.profiler local function cleanName(name) if name:find("/", nil, true) then @@ -109,6 +110,11 @@ function obj.lootremove(name) return true end +mai.profilerEnabled = {direct = true, doc = "function():boolean -- Return whether or not profiler is enabled"} +function obj.profilerEnabled() + return profiler +end + mai.lootattached = {direct = true, doc = "function(name:string):boolean or nil, string -- Check if a loot disk is inserted in the computer."} function obj.lootattached(name) cprint("ocemu.lootattached", name) diff --git a/src/main.lua b/src/main.lua index 79f7501..c1e50c9 100644 --- a/src/main.lua +++ b/src/main.lua @@ -377,8 +377,43 @@ elsa.filesystem.load("apis/component.lua")(env) config.save() +local profilerWindow +local profilerWindowID +local profilerRenderer +local profilerTexture +local SDL = elsa.SDL + +local highdpi=(elsa.args.options.highdpi and 2 or 1) + +function draw_profiler_window() + SDL.setRenderTarget(profilerRenderer, ffi.NULL); + SDL.renderCopy(profilerRenderer, profilerTexture, ffi.NULL, ffi.NULL) + SDL.renderPresent(profilerRenderer) + SDL.setRenderTarget(profilerRenderer, profilerTexture); + -- Actually draw + +end + function boot_machine() -- load machine.lua + if settings.profiler then + profilerWindow = SDL.createWindow("OCEmu - Profiler", 0, 0, 400*highdpi, 200*highdpi, bit32.bor(SDL.WINDOW_SHOWN, SDL.WINDOW_ALLOW_HIGHDPI)) + if profilerWindow == ffi.NULL then + error(ffi.string(SDL.getError())) + end + profilerWindowID = SDL.getWindowID(profilerWindow) + SDL.setWindowFullscreen(profilerWindow, 0) + SDL.restoreWindow(profilerWindow) + SDL.setWindowSize(profilerWindow, 400*highdpi, 200*highdpi) + SDL.setWindowPosition(profilerWindow, 0, 0) + SDL.setWindowGrab(profilerWindow, SDL.FALSE) + profilerRenderer = SDL.createRenderer(profilerWindow, -1, SDL.RENDERER_TARGETTEXTURE) + SDL.setRenderDrawBlendMode(profilerRenderer, SDL.BLENDMODE_BLEND) + profilerTexture = SDL.createTexture(profilerRenderer, SDL.PIXELFORMAT_ARGB8888, SDL.TEXTUREACCESS_TARGET, 400, 200); + if profilerTexture == ffi.NULL then + error(ffi.string(SDL.getError())) + end + end local machine_data, err = elsa.filesystem.read("lua/machine.lua") if machine_data == nil then error("Failed to load machine.lua:\n\t" .. tostring(err)) @@ -480,4 +515,7 @@ function elsa.update(dt) elseif elsa.timer.getTime() >= machine.deadline then resume_thread() end + if settings.profiler then + draw_profiler_window() + end end From f374833fac1550b426f28494427fb3a4579cbbb2 Mon Sep 17 00:00:00 2001 From: Zen1th <39484230+zenith391@users.noreply.github.com> Date: Sat, 8 Jun 2019 18:51:09 +0200 Subject: [PATCH 06/16] Update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bf64d1e..e6508ac 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,10 @@ OCEmu - OpenComputers Emulator ============================== # Features added compared to original -75% - (Optional) Filesystem size limitation -TODO- Close on program termination -TODO- Debugger mode. Useful for coding OSes -TODO- Profiler -TODO- Automatic unit tests +* 95% - (Optional) Filesystem size limitation +* TODO- Debugger mode. Useful for coding OSes +* TODO- Profiler +* TODO- Automatic unit tests Installation ------------ From 02496389fed89fb8b74def2c0a8bad7597c6f97f Mon Sep 17 00:00:00 2001 From: Zen1th <39484230+zenith391@users.noreply.github.com> Date: Sat, 8 Jun 2019 18:51:54 +0200 Subject: [PATCH 07/16] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e6508ac..5ad622e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ OCEmu - OpenComputers Emulator # Features added compared to original * 95% - (Optional) Filesystem size limitation +* 100% - Paste with mouse wheel * TODO- Debugger mode. Useful for coding OSes * TODO- Profiler * TODO- Automatic unit tests From a9b8a5ffdd37ee4f4a2e891bb14a617507340fbb Mon Sep 17 00:00:00 2001 From: zenith391 Date: Thu, 2 Jan 2020 21:04:44 +0100 Subject: [PATCH 08/16] GNOME app --- OCEmu.desktop | 9 +++++++++ icon.png | Bin 0 -> 2487 bytes src/apis/os.lua | 13 ++++++++++++- src/component/drive.lua | 1 + src/main.lua | 1 + 5 files changed, 23 insertions(+), 1 deletion(-) create mode 100755 OCEmu.desktop create mode 100644 icon.png diff --git a/OCEmu.desktop b/OCEmu.desktop new file mode 100755 index 0000000..d5d0b09 --- /dev/null +++ b/OCEmu.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Exec=/usr/bin/lua $OCEMU_PATH/src/boot.lua +GenericName=OCEmu +Icon=$OCEMU_PATH/icon.png +Name=OCEmu +Path=$OCEMU_PATH/src +StartupNotify=true +Terminal=false +Type=Application diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..51acc41bf186d1f496caef4777b1e4495195763b GIT binary patch literal 2487 zcmZXVdpy(oAIHBV8%mLKnHb5XVJ=8#^Zw)WE})QI7387v001Zu z2{g^;DxpupjATI=2xVwBU92TEZCyTD)`-AedD^yq z@3p?bHvir6(fCITsogRg-*l`oxD(24vsKH->${D)^)k;`Dc?%VDNCRiOoHv?=C#?B z)6F3xxw7%-g(dyPr1!1N$n=IeBy0F3S3|8x__x+?&&ZCd4wI|ceIUJV?-xCVQe-(3 zVsOd2rp<~n&wd*aPbMJb0kyS*~GEL!tqf2teuV8iX>RKHIjVsht zXW{q+cNKMUPEs*rMnPJ-zkdfilX}UDVIE`L@V$Dv9($xm!CyJ^6sVpzA}n8S8;OZ3 zy3_*rQTZrD1^Q1qNhmz`;G&|MhIjKatvTh7^7&K#4De_c`ZIXNho#*0%&0l)6Nx{A zh^>2KsePZBp~DzEoeg4u5xb_^JNGI|1Cwf(t_X^C1B?X#Do;Uhn3ZpPPWh-vXa%}} zz&!053A`xlS|#P(Df9eDz&t8F>r{pF`{Je-=owl{7!A1 z2pYq`EzCUyElXc9xu_Bp^VoTYI`{C&$FzSNYpNhKLs#i}8Ygm;T%iF0!~7HXzz}v@ z6S(}r>x;D1i7sfNv!b%ua6x&6d014Y`mS>za#TeHO^M(2dSq(B?G=9FY=E@&>u59e zFfbFskuH6r%5M7{QL(A}&$59~aO2~T!*57)NVexctqp^pF~3A9S>8=xr1p-#K)wwx z`u>vk>8rKne0xiZn_wxjpZ#)!MV`9tv^n`Coa)gxJ1i!#jlg-@m*X zjo(E`QQ3{>)(dUj#JK9@-VW18`OxmFMhp9hZ|qH7!EDWyU%HFpA@aUotzh6drY6 z^9z*2;=e4!rqw&I>|b_IJO4Zz+H-B%+0BJeC@v`EHBM)iBE_HDYi{+fH3_qe4JbV; z*x&pZjr`hL6bzzpg;gS#P)+q~Pac;I@Arge4h|tcKU%THSGU-S8aUn--{&2N>UP|~ zQoa<-F7)2eHTr(fgUZN)PD&!2bS7ATqI)75YaU13NjOFc@A>R% zQgAdmP&|Vp7k`9V1?A{$kal=1^5tZ(ZSO|vBFobv9`}sJ^ge}%!wPKQOq>vpKi=p~ z``}wCGQSD=G2$i+ZPss9Fy0yY=hio=O(9Y7`uh5MdU}S2hB`Vr1_lNuCMFC9gFqm_ z;cz1(BR4m<0|yQqJa{lJF3!%*4hDnS+uNhjXira1A0Ho6Q&UY%O$vn)8yjn5W23IF zuBD|F85yajrbZ@{_wV0tWn~o;6NABEqN1V#0s?}9g7A1elgY&4aAsy^=H}+UzP^VJ z9on~VAB)AJ)9Dcr5f&B}2n2#eBKiCKBdw71yu-oSLE+)yy1Kdsh6WlM8lDu2ub-a> ziG)NV;RoUF?(UYBmchZnVMoF^91e{}Gd4DkXT^JadpkHdxVpMRp-@I1J@nTQZEbC5 zXJ;ajsG_3c=jZF<;*yw{$Y!$>5)z`Lqh0VWC=@C%Fwo1(i%RwJ@F0bRgoNcYu0uEP z{+uZccUK&cFQZA=3gi+90c-$3=>A+F-c5((tt5m)^uR+VB+X(G;v z${Si7eXTNp0~~t+i`PZ6uWYlkEMD;iO;TA|8M5Dhgf^uAeye|`VgFZVKM`*I1ao^X zTGUZXGU~IwJrhs3zOk12N0r0Rx%MBc_J~euxkA1fIqRKz`C(1E_fk5V=4R7BzwJNR zR&cSJ?7!!*YwCGqO?+7#Q-o%E*!{r5+YA1W^k1ON<>1aZxVd`4qWm!BA}P^^{o8*m zF)&HZhQG@>_M^l~MfAmoH?_v>Zq0nQYQaqYmSlypZ{>26qCHy5S~3_un6u>S zkTzv~&Cv98y`-!Nl#Y}{fk5%9>H@f6m$!lO3DRBg=K0hgowE^v)=|gjRwPh>z3pGH z{L8)KRzL$-@Xr&n%U;k}o0~>9gFs~jM z>#Bq{AONneui6|sAHSJ70^p%8qWmzs`lp@5l_a|VGn2h5Ab;K Aa{vGU literal 0 HcmV?d00001 diff --git a/src/apis/os.lua b/src/apis/os.lua index 3360439..b08e057 100644 --- a/src/apis/os.lua +++ b/src/apis/os.lua @@ -55,5 +55,16 @@ function env.os.date(format, ...) if format:sub(1, 1) == "!" then format = format:sub(2) end - return os.date(format, ...) + local ok, err = pcall(function(format, ...) + return os.date(format, ...) + end, format, ...) + if ok then + return err + else + if format == "*t" then + return {year=0,month=1,day=1,hour=0,min=0,sec=0,wday=1,yday=1,isdst=false} + else + return "No date" + end + end end diff --git a/src/component/drive.lua b/src/component/drive.lua index 18b0034..a6c2d4f 100644 --- a/src/component/drive.lua +++ b/src/component/drive.lua @@ -16,6 +16,7 @@ local savePath = directory .. "/" .. address .. ".bin" local platterCount = tier == 3 and 6 or tier == 2 and 4 or 2 local capacity = (tier == 3 and 4096 or tier == 2 and 2048 or 1024) * 1024 local sectorSize = 512 +local speed = tier*2 local sectorCount = capacity / sectorSize local sectorsPerPlatter = sectorCount / platterCount local headPos = 0 diff --git a/src/main.lua b/src/main.lua index c1e50c9..b818636 100644 --- a/src/main.lua +++ b/src/main.lua @@ -109,6 +109,7 @@ machine = { totalMemory = 2*1024*1024, insynccall = false, } +--maxCallBudget = 0.0--5 function machine.consumeCallBudget(callCost) if not settings.fast and not machine.insynccall then From fb194b804cc951a8c7743b51360f48c19abdbf3e Mon Sep 17 00:00:00 2001 From: zenith391 Date: Thu, 18 Jun 2020 23:59:03 +0200 Subject: [PATCH 09/16] buggy things --- src/component/drive.lua | 90 +++++++++++++++++++++++------------------ src/config.lua | 3 ++ src/main.lua | 42 ++++++++++++++++--- src/settings.lua | 3 ++ 4 files changed, 93 insertions(+), 45 deletions(-) diff --git a/src/component/drive.lua b/src/component/drive.lua index a6c2d4f..dd001ac 100644 --- a/src/component/drive.lua +++ b/src/component/drive.lua @@ -1,34 +1,32 @@ -local address, _, directory, label, tier = ... +local address, _, filename, label, tier = ... -compCheckArg(1,directory,"string","nil") +compCheckArg(1,filename,"string","nil") compCheckArg(2,label,"string","nil") compCheckArg(3,tier,"number") -if directory == nil then - directory = elsa.filesystem.getSaveDirectory() +if type(filename) == "string" and not elsa.filesystem.exists(filename) then + error("no such file", 3) end +local directory = elsa.filesystem.getSaveDirectory() .. "/" .. address if not elsa.filesystem.exists(directory) then - elsa.filesystem.createDirectory(directory) + elsa.filesystem.createDirectory(directory) end -local savePath = directory .. "/" .. address .. ".bin" -local platterCount = tier == 3 and 6 or tier == 2 and 4 or 2 -local capacity = (tier == 3 and 4096 or tier == 2 and 2048 or 1024) * 1024 +local savePath = directory .. "/data.bin" +local platterCount = (tier == 0 and 1 or settings.hddPlatterCounts[tier]) +local capacity = (tier == 0 and settings.floppySize or settings.hddSizes[tier]) * 1024 local sectorSize = 512 -local speed = tier*2 local sectorCount = capacity / sectorSize local sectorsPerPlatter = sectorCount / platterCount local headPos = 0 +local speed = tier + 2 local data -local mai = {} -local obj = {} - -local readSectorCosts = {1.0 / 10, 1.0 / 20, 1.0 / 30, 1.0 / 40, 1.0 / 50, 1.0 / 60} -local writeSectorCosts = {1.0 / 5, 1.0 / 10, 1.0 / 15, 1.0 / 20, 1.0 / 25, 1.0 / 30} -local readByteCosts = {1.0 / 48, 1.0 / 64, 1.0 / 80, 1.0 / 96, 1.0 / 112, 1.0 / 128} -local writeByteCosts = {1.0 / 24, 1.0 / 32, 1.0 / 40, 1.0 / 48, 1.0 / 56, 1.0 / 64} +local readSectorCosts = {1/10, 1/20, 1/30, 1/40, 1/50, 1/60} +local writeSectorCosts = {1/5, 1/10, 1/15, 1/20, 1/25, 1/30} +local readByteCosts = {1/48, 1/64, 1/80, 1/96, 1/112, 1/128} +local writeByteCosts = {1/24, 1/32, 1/40, 1/48, 1/56, 1/64} local function save() local file = elsa.filesystem.newFile(savePath, "w") @@ -36,27 +34,33 @@ local function save() file:close() end -local function load() - if not elsa.filesystem.exists(savePath) then - data = string.rep(string.char(0), capacity) - return +local function load(filename) + if elsa.filesystem.exists(filename) then + local file = elsa.filesystem.newFile(filename, "r") + data = file:read("*a"):sub(1, capacity) + file:close() + data = data .. string.rep("\0", capacity - #data) + return true end - local file = elsa.filesystem.newFile(savePath, "r") - data = file:read("*a") - file:close() + return false end -load() +if not load(savePath) then + if type(filename) ~= "string" or not load(filename) then + data = string.rep("\0", capacity) + end + save() +end local function validateSector(sector) if sector < 0 or sector >= sectorCount then - error("invalid offset, not in a usable sector") + error("invalid offset, not in a usable sector", 0) end return sector end -local function offsetSector(sector) - return sector / sectorSize +local function offsetSector(offset) + return offset / sectorSize end local function sectorOffset(sector) @@ -67,8 +71,8 @@ local function checkSector(sector) return validateSector(sector - 1) end -local function checkSectorR(sector) - return validateSector(offsetSector(sector)) +local function checkOffset(offset) + return validateSector(offsetSector(offset - 1)) end local function sectorToHeadPos(sector) @@ -83,6 +87,9 @@ local function moveToSector(sector) return sector end +local mai = {} +local obj = {} + mai.getLabel = {direct = true, doc = "function():string -- Get the current label of the drive."} function obj.getLabel() cprint("drive.getLabel") @@ -129,7 +136,7 @@ function obj.readSector(sector) compCheckArg(1, sector, "number") if not machine.consumeCallBudget(readSectorCosts[speed]) then return end local s = moveToSector(checkSector(sector)) - return string.sub(data, sectorOffset(s), sectorOffset(s) + sectorSize) + return data:sub(sectorOffset(s) + 1, sectorOffset(s) + sectorSize) end mai.writeSector = {direct = true, doc = "function(sector:number, value:string) -- Write the specified contents to the specified sector."} @@ -138,9 +145,10 @@ function obj.writeSector(sector, value) compCheckArg(1, sector, "number") compCheckArg(1, value, "string") if not machine.consumeCallBudget(writeSectorCosts[speed]) then return end + value = value:sub(1, sectorSize) local s = moveToSector(checkSector(sector)) - local a = string.sub(data, 1, sectorOffset(s)) - local b = string.sub(data, sectorOffset(s) + math.min(sectorSize, #value) + 1, capacity) + local a = data:sub(1, sectorOffset(s)) + local b = data:sub(sectorOffset(s) + #value + 1) data = a .. value .. b save() end @@ -150,8 +158,12 @@ function obj.readByte(offset) cprint("drive.readByte", offset) compCheckArg(1, offset, "number") if not machine.consumeCallBudget(readByteCosts[speed]) then return end - local s = moveToSector(checkSectorR(offset)) - return string.byte(string.sub(data, sectorOffset(s), sectorOffset(s))) + moveToSector(checkOffset(offset)) + local byte = data:sub(offset, offset):byte() + if byte >= 128 then + byte = byte - 256 + end + return byte end mai.writeByte = {direct = true, doc = "function(offset:number, value:number) -- Write a single byte to the specified offset."} @@ -160,13 +172,11 @@ function obj.writeByte(offset, value) compCheckArg(1, offset, "number") compCheckArg(1, value, "number") if not machine.consumeCallBudget(writeByteCosts[speed]) then return end - local s = moveToSector(checkSectorR(offset)) - local a = string.sub(data, 1, sectorOffset(s)) - local b = string.sub(data, sectorOffset(s) + 2, capacity) - data = a .. string.char(value) .. b + moveToSector(checkOffset(offset)) + local a = data:sub(1, offset - 1) + local b = data:sub(offset + 1) + data = a .. string.char(math.floor(value % 256)) .. b save() end - return obj, nil, mai - diff --git a/src/config.lua b/src/config.lua index f1a29a0..d4ca025 100644 --- a/src/config.lua +++ b/src/config.lua @@ -18,6 +18,9 @@ local comments = { ["emulator.debug"]="Whether to enable the emulator's extremely verbose logging.", ["emulator.fast"]="Whether to choose performance over timing-accuracy.", ["emulator.vague"]="Whether to return vague error messages like OpenComputers.", +["filesystem.floppySize"]="The size of writable floppy disks, in kilobytes.", +["filesystem.hddPlatterCounts"]="Number of physical platters to pretend a disk has in unmanaged mode. This controls seek times, in how it emulates sectors overlapping (thus sharing a common head position for access).", +["filesystem.hddSizes"]="The sizes of the three tiers of hard drives, in kilobytes. This list must contain exactly three entries, or it will be ignored.", ["emulator.profiler"]="Whether to enable real-time profiler or not.", ["filesystem.maxReadBuffer"]="The maximum block size that can be read in one 'read' call on a file system. This is used to limit the amount of memory a call from a user program can cause to be allocated on the host side: when 'read' is, called a byte array with the specified size has to be allocated. So if this weren't limited, a Lua program could trigger massive memory allocations regardless of the amount of RAM installed in the computer it runs on. As a side effect this pretty much determines the read performance of file systems.", ["internet.enableHttp"]="Whether to allow HTTP requests via internet cards. When enabled, the `request` method on internet card components becomes available.", diff --git a/src/main.lua b/src/main.lua index b818636..17e60d3 100644 --- a/src/main.lua +++ b/src/main.lua @@ -96,8 +96,19 @@ if settings.components == nil then config.set("emulator.components",settings.components) end -if settings.profiler then - +local memoryUsages = {} + +local profilerHook = function(event) + local func = debug.getinfo(2) + if func == nil then return end + local name = func.name + if name ~= nil then + if event == "call" or event == "tail call" then + memoryUsages[name] = collectgarbage("count") + elseif event == "return" then + memoryUsages[name] = collectgarbage("count") - memoryUsages[name] + end + end end local maxCallBudget = (1.5 + 1.5 + 1.5) / 3 -- T3 CPU and 2 T3+ memory @@ -109,7 +120,7 @@ machine = { totalMemory = 2*1024*1024, insynccall = false, } ---maxCallBudget = 0.0--5 +--maxCallBudget = 0.5 function machine.consumeCallBudget(callCost) if not settings.fast and not machine.insynccall then @@ -216,7 +227,13 @@ local env = { }, collectgarbage = collectgarbage, coroutine = { - create = coroutine.create, + create = function(...) + local c = coroutine.create(...) + if settings.profiler then + debug.sethook(c, profilerHook, "cr") + end + return c + end, resume = coroutine.resume, running = coroutine.running, status = coroutine.status, @@ -232,7 +249,18 @@ local env = { getregistry = debug.getregistry, getupvalue = debug.getupvalue, getuservalue = debug.getuservalue, - sethook = debug.sethook, + sethook = function(...) + if not select(1, ...) then + if settings.profiler then + cprint("attempt to clear hooks") + debug.sethook() + cprint("adding profiler hook") + debug.sethook(profilerHook, "cr") + end + else + debug.sethook(...) + end + end, setlocal = debug.setlocal, setmetatable = debug.setmetatable, setupvalue = debug.setupvalue, @@ -424,6 +452,10 @@ function boot_machine() error("Failed to parse machine.lua\n\t" .. tostring(err)) end machine.thread = coroutine.create(machine_fn) + if settings.profiler then + print("hook profiler to machine thread") + debug.sethook(machine.thread, profilerHook, "cr") + end local results = { coroutine.resume(machine.thread) } if results[1] then if #results ~= 1 then diff --git a/src/settings.lua b/src/settings.lua index 5df3c8e..44db254 100644 --- a/src/settings.lua +++ b/src/settings.lua @@ -14,6 +14,9 @@ settings = { fast = config.get("emulator.fast",true), vagueErrors = config.get("emulator.vague",true), + floppySize = config.get("filesystem.floppySize",512), + hddPlatterCounts = config.get("filesystem.hddPlatterCounts",{2,4,8}), + hddSizes = config.get("filesystem.hddSizes",{1024,2048,4096}), maxReadBuffer = config.get("filesystem.maxReadBuffer",2048), httpEnabled = config.get("internet.enableHttp",true), From 13b763995c0a7d9d89bd884a371c544e5300d804 Mon Sep 17 00:00:00 2001 From: zenith391 Date: Fri, 19 Jun 2020 00:09:39 +0200 Subject: [PATCH 10/16] Revert to master --- README.md | 10 ---------- src/apis/os.lua | 13 +------------ src/main.lua | 43 ------------------------------------------- 3 files changed, 1 insertion(+), 65 deletions(-) diff --git a/README.md b/README.md index b3aafdd..fe98f68 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,6 @@ OCEmu - OpenComputers Emulator ============================== -# Features added compared to original -* 95% - (Optional) Filesystem size limitation -* 100% - Paste with mouse wheel -* TODO- Debugger mode. Useful for coding OSes -* TODO- Profiler -* TODO- Automatic unit tests - Installation ------------ @@ -95,6 +88,3 @@ If you want to use a custom path (for example, for running multiple machines wit cd src lua boot.lua /path/to/my/emulated/machine_a ``` - -## The fork on itself -The fork on itself is a update of the, now to seem, not working on project by gamax92. In this fork i'm going to try to fix all known bugs and to verify and add existing pull requests, and also treat issues on my own. diff --git a/src/apis/os.lua b/src/apis/os.lua index b08e057..3360439 100644 --- a/src/apis/os.lua +++ b/src/apis/os.lua @@ -55,16 +55,5 @@ function env.os.date(format, ...) if format:sub(1, 1) == "!" then format = format:sub(2) end - local ok, err = pcall(function(format, ...) - return os.date(format, ...) - end, format, ...) - if ok then - return err - else - if format == "*t" then - return {year=0,month=1,day=1,hour=0,min=0,sec=0,wday=1,yday=1,isdst=false} - else - return "No date" - end - end + return os.date(format, ...) end diff --git a/src/main.lua b/src/main.lua index b818636..16f2372 100644 --- a/src/main.lua +++ b/src/main.lua @@ -96,10 +96,6 @@ if settings.components == nil then config.set("emulator.components",settings.components) end -if settings.profiler then - -end - local maxCallBudget = (1.5 + 1.5 + 1.5) / 3 -- T3 CPU and 2 T3+ memory machine = { @@ -109,7 +105,6 @@ machine = { totalMemory = 2*1024*1024, insynccall = false, } ---maxCallBudget = 0.0--5 function machine.consumeCallBudget(callCost) if not settings.fast and not machine.insynccall then @@ -378,43 +373,8 @@ elsa.filesystem.load("apis/component.lua")(env) config.save() -local profilerWindow -local profilerWindowID -local profilerRenderer -local profilerTexture -local SDL = elsa.SDL - -local highdpi=(elsa.args.options.highdpi and 2 or 1) - -function draw_profiler_window() - SDL.setRenderTarget(profilerRenderer, ffi.NULL); - SDL.renderCopy(profilerRenderer, profilerTexture, ffi.NULL, ffi.NULL) - SDL.renderPresent(profilerRenderer) - SDL.setRenderTarget(profilerRenderer, profilerTexture); - -- Actually draw - -end - function boot_machine() -- load machine.lua - if settings.profiler then - profilerWindow = SDL.createWindow("OCEmu - Profiler", 0, 0, 400*highdpi, 200*highdpi, bit32.bor(SDL.WINDOW_SHOWN, SDL.WINDOW_ALLOW_HIGHDPI)) - if profilerWindow == ffi.NULL then - error(ffi.string(SDL.getError())) - end - profilerWindowID = SDL.getWindowID(profilerWindow) - SDL.setWindowFullscreen(profilerWindow, 0) - SDL.restoreWindow(profilerWindow) - SDL.setWindowSize(profilerWindow, 400*highdpi, 200*highdpi) - SDL.setWindowPosition(profilerWindow, 0, 0) - SDL.setWindowGrab(profilerWindow, SDL.FALSE) - profilerRenderer = SDL.createRenderer(profilerWindow, -1, SDL.RENDERER_TARGETTEXTURE) - SDL.setRenderDrawBlendMode(profilerRenderer, SDL.BLENDMODE_BLEND) - profilerTexture = SDL.createTexture(profilerRenderer, SDL.PIXELFORMAT_ARGB8888, SDL.TEXTUREACCESS_TARGET, 400, 200); - if profilerTexture == ffi.NULL then - error(ffi.string(SDL.getError())) - end - end local machine_data, err = elsa.filesystem.read("lua/machine.lua") if machine_data == nil then error("Failed to load machine.lua:\n\t" .. tostring(err)) @@ -516,7 +476,4 @@ function elsa.update(dt) elseif elsa.timer.getTime() >= machine.deadline then resume_thread() end - if settings.profiler then - draw_profiler_window() - end end From fb8500d2f26bd7e5c8c15a3441e80ff81e8388cc Mon Sep 17 00:00:00 2001 From: zenith391 Date: Fri, 19 Jun 2020 00:14:20 +0200 Subject: [PATCH 11/16] Revert ocemu and settings --- src/component/ocemu.lua | 6 ------ src/settings.lua | 1 - 2 files changed, 7 deletions(-) diff --git a/src/component/ocemu.lua b/src/component/ocemu.lua index 0a5348d..31457fb 100644 --- a/src/component/ocemu.lua +++ b/src/component/ocemu.lua @@ -4,7 +4,6 @@ component.connect("filesystem", gen_uuid(), -1, "customlua/ocemu", "ocemu", true, 5) local components = settings.components -local profiler = settings.profiler local function cleanName(name) if name:find("/", nil, true) then @@ -110,11 +109,6 @@ function obj.lootremove(name) return true end -mai.profilerEnabled = {direct = true, doc = "function():boolean -- Return whether or not profiler is enabled"} -function obj.profilerEnabled() - return profiler -end - mai.lootattached = {direct = true, doc = "function(name:string):boolean or nil, string -- Check if a loot disk is inserted in the computer."} function obj.lootattached(name) cprint("ocemu.lootattached", name) diff --git a/src/settings.lua b/src/settings.lua index 44db254..5136f85 100644 --- a/src/settings.lua +++ b/src/settings.lua @@ -24,7 +24,6 @@ settings = { maxNetworkPacketSize = config.get("misc.maxNetworkPacketSize",8192), maxWirelessRange = config.get("misc.maxWirelessRange",400), - profiler = config.get("emulator.profiler",false), } if settings.monochromeColor == nil then From bdc6d99b7f91d7567af2e79a34860d0035fab43a Mon Sep 17 00:00:00 2001 From: zenith391 Date: Fri, 19 Jun 2020 01:51:32 +0200 Subject: [PATCH 12/16] GPU buffers Still need to be made better, but the draft in itself works --- src/component/gpu.lua | 182 +++++++++++++++++++++++++++++++++- src/component/screen_sdl2.lua | 14 +++ 2 files changed, 192 insertions(+), 4 deletions(-) diff --git a/src/component/gpu.lua b/src/component/gpu.lua index 4e4f930..5ec5494 100644 --- a/src/component/gpu.lua +++ b/src/component/gpu.lua @@ -7,6 +7,7 @@ local utf8 = require("lua-utf8") local bindaddress local depthTbl = {1,4,8} +local vramTbl = {1,2,3} local rdepthTbl = {1,[4]=2,[8]=3} local depthNames = {"OneBit","FourBit","EightBit"} @@ -21,6 +22,161 @@ local fillCosts = {1/32, 1/64, 1/128} local mai = {} local obj = {} +local activeBufferIdx = 0 -- 0 = screen +local buffers = {} +local totalMemory = maxwidth*maxheight*vramTbl[maxtier] -- TODO set +local usedMemory = 0 + +local function bufferSet(buf, x, y, char, fg, bg) + local pos = (y-1) * buf.width + x + fg = fg or buf.fg + bg = bg or buf.bg + buf.foreground[pos] = fg + buf.background[pos] = bg + local before = buf.text:sub(1, pos-1) + local after = buf.text:sub(pos+1) + buf.text = before .. char .. after + buf.dirty = true +end + +local function bufferGet(buf, x, y) + local pos = (y-1) * buf.width + x + local char = utf8.sub(buf.text, pos, pos) + local fg = buf.foreground[pos] or 0xFFFFFF + local bg = buf.background[pos] or 0 + return char, fg, bg +end + +mai.allocateBuffer = {direct = true, doc = "function([width: number, height: number]): number -- allocates a new buffer with dimensions width*height (defaults to max resolution) and appends it to the buffer list. Returns the index of the new buffer and returns nil with an error message on failure. A buffer can be allocated even when there is no screen bound to this gpu. Index 0 is always reserved for the screen and thus the lowest index of an allocated buffer is always 1."} +function obj.allocateBuffer(width, height) + cprint("gpu.allocateBuffer", width, height) + width = width or maxwidth + height = height or maxheight + + if width <= 0 or height <= 0 then + return false, "invalid page dimensions: must be greater than zero" + end + + local size = width*height + if usedMemory+size > totalMemory then + return false, "not enough video memory" + end + local buffer = { + text = (" "):rep(width*height), + foreground = {}, + background = {}, + width = width, + height = height, + size = width*height, + dirty = true, + fg = 0xFFFFFF, + bg = 0x000000, + bufferGet = bufferGet -- exposure of API for screen_sdl2 + } + usedMemory = usedMemory + size + table.insert(buffers, buffer) + return #buffers +end + +mai.freeBuffer = {direct = true, doc = "function(index: number): boolean -- Closes buffer at `index`. Returns true if a buffer closed. If the current buffer is closed, index moves to 0"} +function obj.freeBuffer(idx) + if not buffers[idx] then + return false, "no buffer at index" + else + if idx == activeBufferIdx then + idx = 0 + end + usedMemory = usedMemory - buffers[idx].size + buffers[idx] = nil + return true + end +end + +mai.freeAllBuffers = {direct = true, doc = "function(): number -- Closes all buffers and returns the count. If the active buffer is closed, index moves to 0"} +function obj.freeAllBuffers() + local count = #buffers + activeBufferIdx = 0 + buffers = {} + usedMemory = 0 + return count +end + +mai.buffers = {direct = true, doc = "function(): number -- Returns an array of indexes of the allocated buffers"} +function obj.buffers() + local array = {} + for k, v in pairs(buffers) do + table.insert(array, k) + end + return array +end + +mai.getActiveBuffer = {direct = true, doc = "function(): number -- returns the index of the currently selected buffer. 0 is reserved for the screen. Can return 0 even when there is no screen"} +function obj.getActiveBuffer() + return activeBufferIdx +end + +mai.setActiveBuffer = {direct = true, doc = "function(index: number): number -- Sets the active buffer to `index`. 1 is the first vram buffer and 0 is reserved for the screen. returns nil for invalid index (0 is always valid)"} +function obj.setActiveBuffer(idx) + if idx ~= 0 and not buffers[idx] then + return nil + else + activeBufferIdx = idx + end +end + +mai.freeMemory = {direct = true, doc = "function(): number -- returns the total free memory not allocated to buffers. This does not include the screen."} +function obj.freeMemory() + return totalMemory - usedMemory +end + +mai.totalMemory = {direct = true, doc = "function(): number -- returns the total memory size of the gpu vram. This does not include the screen."} +function obj.totalMemory() + return totalMemory +end + +mai.getBufferSize = {direct = true, doc = "function(index: number): number, number -- returns the buffer size at index. Returns the screen resolution for index 0. returns nil for invalid indexes"} +function obj.getBufferSize(idx) + if idx == 0 then + return obj.getResolution() + else + local buf = buffers[idx] + if buf then + return buf.width, buf.height + else + return nil + end + end +end + +mai.bitblt = {direct = true, doc = "function([dst: number, col: number, row: number, width: number, height: number, src: number, fromCol: number, fromRow: number]):boolean -- bitblt from buffer to screen. All parameters are optional. Writes to `dst` page in rectangle `x, y, width, height`, defaults to the bound screen and its viewport. Reads data from `src` page at `fx, fy`, default is the active page from position 1, 1"} +function obj.bitblt(dst, col, row, width, height, src, fromCol, fromRow) + dst = dst or 0 + col = col or 1 + row = row or 1 + src = src or activeBufferIdx + fromCol = fromCol or 1 + fromRow = fromRow or 1 + + if dst == 0 then + if bindaddress == nil then + return nil, "no screen" + end + width, height = component.cecinvoke(bindaddress, "getResolution") + if src == 0 then + -- TODO act as copy() + else + local buf = buffers[src] + if not buf then + return nil + end + width, height = math.min(buf.width, width), math.min(buf.height, height) + component.cecinvoke(bindaddress, "bitblt", buf, col, row, width, height, fromCol, fromRow) + end + else + + end +end + mai.bind = {doc = "function(address:string):boolean -- Binds the GPU to the screen with the specified address."} function obj.bind(address, reset) cprint("gpu.bind", address, reset) @@ -42,6 +198,9 @@ function obj.bind(address, reset) component.cecinvoke(bindaddress, "setDepth", math.min(component.cecinvoke(bindaddress, "maxDepth"), maxtier)) component.cecinvoke(bindaddress, "setForeground", 0xFFFFFF) component.cecinvoke(bindaddress, "setBackground", 0x000000) + buffers = {} + usedMemory = 0 + activeBufferIdx = 0 end end @@ -260,17 +419,21 @@ function obj.get(x, y) if bindaddress == nil then return nil, "no screen" end - local w,h = component.cecinvoke(bindaddress, "getResolution") + local w,h = obj.getResolution() if x < 1 or x >= w+1 or y < 1 or y >= h+1 then error("index out of bounds", 0) end - return component.cecinvoke(bindaddress, "get", x, y) + if activeBufferIdx == 0 then + return component.cecinvoke(bindaddress, "get", x, y) + else + return bufferGet(buffers[activeBufferIdx], x, y) + end end mai.set = {direct = true, doc = "function(x:number, y:number, value:string[, vertical:boolean]):boolean -- Plots a string value to the screen at the specified position. Optionally writes the string vertically."} function obj.set(x, y, value, vertical) cprint("gpu.set", x, y, value, vertical) - if not machine.consumeCallBudget(setCosts[maxtier]) then return end + if activeBufferIdx == 0 and not machine.consumeCallBudget(setCosts[maxtier]) then return end compCheckArg(1,x,"number") compCheckArg(2,y,"number") compCheckArg(3,value,"string") @@ -278,7 +441,18 @@ function obj.set(x, y, value, vertical) if bindaddress == nil then return nil, "no screen" end - return component.cecinvoke(bindaddress, "set", x, y, value, vertical) + if activeBufferIdx == 0 then + return component.cecinvoke(bindaddress, "set", x, y, value, vertical) + else + for i=1, utf8.len(value) do + local ch = utf8.sub(value, i, i) + if vertical then + bufferSet(buffers[activeBufferIdx], x, y+i-1, ch) + else + bufferSet(buffers[activeBufferIdx], x+i-1, y, ch) + end + end + end end mai.copy = {direct = true, doc = "function(x:number, y:number, width:number, height:number, tx:number, ty:number):boolean -- Copies a portion of the screen from the specified location with the specified size by the specified translation."} diff --git a/src/component/screen_sdl2.lua b/src/component/screen_sdl2.lua index 99f31f8..5470dec 100644 --- a/src/component/screen_sdl2.lua +++ b/src/component/screen_sdl2.lua @@ -504,6 +504,20 @@ function cec.fill(x1, y1, w, h, char) -- Fills a portion of the screen at the sp end return true end +function cec.bitblt(buf, col, row, width, height, fromCol, fromRow) + cprint("(cec) screen.bitblt") + for x=0, width-1 do + for y=0, height-1 do + local char, fg, bg = buf:bufferGet(x+fromCol, y+fromRow) + local dx = x+col + local dy = y+row + if dx >= 1 and dx <= width and dy >= 1 and dy <= height then + cprint("cece", tostring(char)) + setPos(dx, dy, utf8.byte(char), fg, bg) + end + end + end +end function cec.getResolution() -- Get the current screen resolution. cprint("(cec) screen.getResolution") return width, height From f091f8b08b54487b3b90b27b2eb443075bd3a792 Mon Sep 17 00:00:00 2001 From: zenith391 Date: Fri, 19 Jun 2020 11:50:44 +0200 Subject: [PATCH 13/16] Fill on buffers --- src/component/gpu.lua | 65 +++++++++++++++++++++++++++++------ src/component/screen_sdl2.lua | 9 +++-- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/component/gpu.lua b/src/component/gpu.lua index 5ec5494..779c15e 100644 --- a/src/component/gpu.lua +++ b/src/component/gpu.lua @@ -24,7 +24,7 @@ local obj = {} local activeBufferIdx = 0 -- 0 = screen local buffers = {} -local totalMemory = maxwidth*maxheight*vramTbl[maxtier] -- TODO set +local totalMemory = maxwidth*maxheight*vramTbl[maxtier] local usedMemory = 0 local function bufferSet(buf, x, y, char, fg, bg) @@ -47,6 +47,14 @@ local function bufferGet(buf, x, y) return char, fg, bg end +local function consumeGraphicCallBudget(cost) + if activeBufferIdx == 0 then + return machine.consumeCallBudget(cost) + else + return true + end +end + mai.allocateBuffer = {direct = true, doc = "function([width: number, height: number]): number -- allocates a new buffer with dimensions width*height (defaults to max resolution) and appends it to the buffer list. Returns the index of the new buffer and returns nil with an error message on failure. A buffer can be allocated even when there is no screen bound to this gpu. Index 0 is always reserved for the screen and thus the lowest index of an allocated buffer is always 1."} function obj.allocateBuffer(width, height) cprint("gpu.allocateBuffer", width, height) @@ -117,6 +125,7 @@ end mai.setActiveBuffer = {direct = true, doc = "function(index: number): number -- Sets the active buffer to `index`. 1 is the first vram buffer and 0 is reserved for the screen. returns nil for invalid index (0 is always valid)"} function obj.setActiveBuffer(idx) + cprint("gpu.setActiveBuffer", idx) if idx ~= 0 and not buffers[idx] then return nil else @@ -150,6 +159,7 @@ end mai.bitblt = {direct = true, doc = "function([dst: number, col: number, row: number, width: number, height: number, src: number, fromCol: number, fromRow: number]):boolean -- bitblt from buffer to screen. All parameters are optional. Writes to `dst` page in rectangle `x, y, width, height`, defaults to the bound screen and its viewport. Reads data from `src` page at `fx, fy`, default is the active page from position 1, 1"} function obj.bitblt(dst, col, row, width, height, src, fromCol, fromRow) + cprint("gpu.bitblt", dst, col, row, width, height, src, fromCol, fromRow) dst = dst or 0 col = col or 1 row = row or 1 @@ -161,7 +171,13 @@ function obj.bitblt(dst, col, row, width, height, src, fromCol, fromRow) if bindaddress == nil then return nil, "no screen" end - width, height = component.cecinvoke(bindaddress, "getResolution") + if not width or not height then + local rw, rh = component.cecinvoke(bindaddress, "getResolution") + width = width or rw + height = height or rh + end + + -- TODO consume call budget if src == 0 then -- TODO act as copy() else @@ -169,6 +185,7 @@ function obj.bitblt(dst, col, row, width, height, src, fromCol, fromRow) if not buf then return nil end + buf.dirty = false width, height = math.min(buf.width, width), math.min(buf.height, height) component.cecinvoke(bindaddress, "bitblt", buf, col, row, width, height, fromCol, fromRow) end @@ -210,14 +227,18 @@ function obj.getForeground() if bindaddress == nil then return nil, "no screen" end - return component.cecinvoke(bindaddress, "getForeground") + if activeBufferIdx == 0 then + return component.cecinvoke(bindaddress, "getForeground") + else + return buffers[activeBufferIdx].fg + end end mai.setForeground = {direct = true, doc = "function(value:number[, palette:boolean]):number, number or nil -- Sets the foreground color to the specified value. Optionally takes an explicit palette index. Returns the old value and if it was from the palette its palette index."} function obj.setForeground(value, palette) cprint("gpu.setForeground", value, palette) - if not machine.consumeCallBudget(setForegroundCosts[maxtier]) then return end + if consumeGraphicCallBudget(setForegroundCosts[maxtier]) then return end compCheckArg(1,value,"number") compCheckArg(2,palette,"boolean","nil") if bindaddress == nil then @@ -229,7 +250,11 @@ function obj.setForeground(value, palette) if palette == true and (value < 0 or value > 15) then error("invalid palette index", 0) end - return component.cecinvoke(bindaddress, "setForeground", value, palette) + if activeBufferIdx == 0 then + return component.cecinvoke(bindaddress, "setForeground", value, palette) + else + buffers[activeBufferIdx].fg = value + end end mai.getBackground = {direct = true, doc = "function():number, boolean -- Get the current background color and whether it's from the palette or not."} @@ -238,13 +263,17 @@ function obj.getBackground() if bindaddress == nil then return nil, "no screen" end - return component.cecinvoke(bindaddress, "getBackground") + if activeBufferIdx == 0 then + return component.cecinvoke(bindaddress, "getBackground") + else + return buffers[activeBufferIdx].bg + end end mai.setBackground = {direct = true, doc = "function(value:number[, palette:boolean]):number, number or nil -- Sets the background color to the specified value. Optionally takes an explicit palette index. Returns the old value and if it was from the palette its palette index."} function obj.setBackground(value, palette) cprint("gpu.setBackground", value, palette) - if not machine.consumeCallBudget(setBackgroundCosts[maxtier]) then return end + if not consumeGraphicCallBudget(setBackgroundCosts[maxtier]) then return end compCheckArg(1,value,"number") compCheckArg(2,palette,"boolean","nil") if bindaddress == nil then @@ -257,7 +286,11 @@ function obj.setBackground(value, palette) if palette and (value < 0 or value > 15) then error("invalid palette index", 0) end - return component.cecinvoke(bindaddress, "setBackground", value, palette) + if activeBufferIdx == 0 then + return component.cecinvoke(bindaddress, "setBackground", value, palette) + else + buffers[activeBufferIdx].bg = value + end end mai.getDepth = {direct = true, doc = "function():number -- Returns the currently set color depth."} @@ -292,7 +325,7 @@ end mai.fill = {direct = true, doc = "function(x:number, y:number, width:number, height:number, char:string):boolean -- Fills a portion of the screen at the specified position with the specified size with the specified character."} function obj.fill(x, y, width, height, char) cprint("gpu.fill", x, y, width, height, char) - if not machine.consumeCallBudget(fillCosts[maxtier]) then return end + if activeBufferIdx == 0 and not machine.consumeCallBudget(fillCosts[maxtier]) then return end compCheckArg(1,x,"number") compCheckArg(2,y,"number") compCheckArg(3,width,"number") @@ -304,7 +337,17 @@ function obj.fill(x, y, width, height, char) if utf8.len(char) ~= 1 then return nil, "invalid fill value" end - return component.cecinvoke(bindaddress, "fill", x, y, width, height, char) + if activeBufferIdx == 0 then + return component.cecinvoke(bindaddress, "fill", x, y, width, height, char) + else + local buf = buffers[activeBufferIdx] + for dx=0, width-1 do + for dy=0, height-1 do + bufferSet(buf, x+dx, y+dy, char) + end + end + return true + end end mai.getScreen = {direct = true, doc = "function():string -- Get the address of the screen the GPU is currently bound to."} @@ -433,7 +476,7 @@ end mai.set = {direct = true, doc = "function(x:number, y:number, value:string[, vertical:boolean]):boolean -- Plots a string value to the screen at the specified position. Optionally writes the string vertically."} function obj.set(x, y, value, vertical) cprint("gpu.set", x, y, value, vertical) - if activeBufferIdx == 0 and not machine.consumeCallBudget(setCosts[maxtier]) then return end + if consumeGraphicCallBudget(setCosts[maxtier]) then return end compCheckArg(1,x,"number") compCheckArg(2,y,"number") compCheckArg(3,value,"string") diff --git a/src/component/screen_sdl2.lua b/src/component/screen_sdl2.lua index 5470dec..1923e56 100644 --- a/src/component/screen_sdl2.lua +++ b/src/component/screen_sdl2.lua @@ -505,18 +505,23 @@ function cec.fill(x1, y1, w, h, char) -- Fills a portion of the screen at the sp return true end function cec.bitblt(buf, col, row, width, height, fromCol, fromRow) - cprint("(cec) screen.bitblt") + cprint("(cec) screen.bitblt", tostring(buf), col, row, width, height, fromCol, fromRow) + local oldFg = srcfgc + local oldBg = srcbgc for x=0, width-1 do for y=0, height-1 do local char, fg, bg = buf:bufferGet(x+fromCol, y+fromRow) local dx = x+col local dy = y+row if dx >= 1 and dx <= width and dy >= 1 and dy <= height then - cprint("cece", tostring(char)) + srcfgc = fg + srcbgc = bg setPos(dx, dy, utf8.byte(char), fg, bg) end end end + srcfgc = oldFg + srcbgc = oldBg end function cec.getResolution() -- Get the current screen resolution. cprint("(cec) screen.getResolution") From bbae8c26b6e90a5da8daa65c998549020211242f Mon Sep 17 00:00:00 2001 From: zenith391 Date: Fri, 19 Jun 2020 13:41:29 +0200 Subject: [PATCH 14/16] Fixed buffer positioning --- src/component/gpu.lua | 18 +++++++++++------- src/component/screen_sdl2.lua | 10 ++++++---- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/component/gpu.lua b/src/component/gpu.lua index 779c15e..4552e3b 100644 --- a/src/component/gpu.lua +++ b/src/component/gpu.lua @@ -7,7 +7,6 @@ local utf8 = require("lua-utf8") local bindaddress local depthTbl = {1,4,8} -local vramTbl = {1,2,3} local rdepthTbl = {1,[4]=2,[8]=3} local depthNames = {"OneBit","FourBit","EightBit"} @@ -24,19 +23,23 @@ local obj = {} local activeBufferIdx = 0 -- 0 = screen local buffers = {} -local totalMemory = maxwidth*maxheight*vramTbl[maxtier] +local totalMemory = maxwidth*maxheight*maxtier local usedMemory = 0 local function bufferSet(buf, x, y, char, fg, bg) + if x > buf.width or y > buf.height or x < 1 or y < 1 then + return false + end local pos = (y-1) * buf.width + x fg = fg or buf.fg bg = bg or buf.bg buf.foreground[pos] = fg buf.background[pos] = bg - local before = buf.text:sub(1, pos-1) - local after = buf.text:sub(pos+1) + local before = utf8.sub(buf.text, 1, pos-1) + local after = utf8.sub(buf.text, pos+1) buf.text = before .. char .. after buf.dirty = true + return true end local function bufferGet(buf, x, y) @@ -88,6 +91,7 @@ end mai.freeBuffer = {direct = true, doc = "function(index: number): boolean -- Closes buffer at `index`. Returns true if a buffer closed. If the current buffer is closed, index moves to 0"} function obj.freeBuffer(idx) + cprint("gpu.freeBuffer", idx) if not buffers[idx] then return false, "no buffer at index" else @@ -238,7 +242,7 @@ end mai.setForeground = {direct = true, doc = "function(value:number[, palette:boolean]):number, number or nil -- Sets the foreground color to the specified value. Optionally takes an explicit palette index. Returns the old value and if it was from the palette its palette index."} function obj.setForeground(value, palette) cprint("gpu.setForeground", value, palette) - if consumeGraphicCallBudget(setForegroundCosts[maxtier]) then return end + if not consumeGraphicCallBudget(setForegroundCosts[maxtier]) then return end compCheckArg(1,value,"number") compCheckArg(2,palette,"boolean","nil") if bindaddress == nil then @@ -325,7 +329,7 @@ end mai.fill = {direct = true, doc = "function(x:number, y:number, width:number, height:number, char:string):boolean -- Fills a portion of the screen at the specified position with the specified size with the specified character."} function obj.fill(x, y, width, height, char) cprint("gpu.fill", x, y, width, height, char) - if activeBufferIdx == 0 and not machine.consumeCallBudget(fillCosts[maxtier]) then return end + if not consumeGraphicCallBudget(fillCosts[maxtier]) then return end compCheckArg(1,x,"number") compCheckArg(2,y,"number") compCheckArg(3,width,"number") @@ -476,7 +480,7 @@ end mai.set = {direct = true, doc = "function(x:number, y:number, value:string[, vertical:boolean]):boolean -- Plots a string value to the screen at the specified position. Optionally writes the string vertically."} function obj.set(x, y, value, vertical) cprint("gpu.set", x, y, value, vertical) - if consumeGraphicCallBudget(setCosts[maxtier]) then return end + if not consumeGraphicCallBudget(setCosts[maxtier]) then return end compCheckArg(1,x,"number") compCheckArg(2,y,"number") compCheckArg(3,value,"string") diff --git a/src/component/screen_sdl2.lua b/src/component/screen_sdl2.lua index 1923e56..3916cc3 100644 --- a/src/component/screen_sdl2.lua +++ b/src/component/screen_sdl2.lua @@ -504,21 +504,23 @@ function cec.fill(x1, y1, w, h, char) -- Fills a portion of the screen at the sp end return true end -function cec.bitblt(buf, col, row, width, height, fromCol, fromRow) - cprint("(cec) screen.bitblt", tostring(buf), col, row, width, height, fromCol, fromRow) +function cec.bitblt(buf, col, row, w, h, fromCol, fromRow) + cprint("(cec) screen.bitblt", tostring(buf), col, row, w, h, fromCol, fromRow) local oldFg = srcfgc local oldBg = srcbgc - for x=0, width-1 do - for y=0, height-1 do + for y=0, h-1 do + for x=0, w-1 do local char, fg, bg = buf:bufferGet(x+fromCol, y+fromRow) local dx = x+col local dy = y+row if dx >= 1 and dx <= width and dy >= 1 and dy <= height then srcfgc = fg srcbgc = bg + io.stdout:write(char) setPos(dx, dy, utf8.byte(char), fg, bg) end end + print("") end srcfgc = oldFg srcbgc = oldBg From 8d00466823c9d6858123e209ba4da488dec2ed0c Mon Sep 17 00:00:00 2001 From: zenith391 Date: Sat, 20 Jun 2020 11:10:42 +0200 Subject: [PATCH 15/16] Bitblt call budget --- src/component/gpu.lua | 22 +++++++++++++++++++--- src/component/screen_sdl2.lua | 2 -- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/component/gpu.lua b/src/component/gpu.lua index 4552e3b..d1bcf4a 100644 --- a/src/component/gpu.lua +++ b/src/component/gpu.lua @@ -16,6 +16,7 @@ local setPaletteColorCosts = {1/2, 1/8, 1/16} local setCosts = {1/64, 1/128, 1/256} local copyCosts = {1/16, 1/32, 1/64} local fillCosts = {1/32, 1/64, 1/128} +local bitbltCost = 0.5 -- gpu component local mai = {} @@ -95,11 +96,11 @@ function obj.freeBuffer(idx) if not buffers[idx] then return false, "no buffer at index" else + usedMemory = usedMemory - buffers[idx].size + buffers[idx] = nil if idx == activeBufferIdx then idx = 0 end - usedMemory = usedMemory - buffers[idx].size - buffers[idx] = nil return true end end @@ -161,6 +162,18 @@ function obj.getBufferSize(idx) end end +local function determineBitbltBudgetCost(src, dst) + if dst ~= "screen" then -- write to buffer from buffer/screen are free + return 0 + elseif src == "screen" then + return 0 + elseif src.dirty then + return bitbltCost * (src.width * src.height) / (maxwidth * maxheight) + elseif not src.dirty then + return 0.001 + end +end + mai.bitblt = {direct = true, doc = "function([dst: number, col: number, row: number, width: number, height: number, src: number, fromCol: number, fromRow: number]):boolean -- bitblt from buffer to screen. All parameters are optional. Writes to `dst` page in rectangle `x, y, width, height`, defaults to the bound screen and its viewport. Reads data from `src` page at `fx, fy`, default is the active page from position 1, 1"} function obj.bitblt(dst, col, row, width, height, src, fromCol, fromRow) cprint("gpu.bitblt", dst, col, row, width, height, src, fromCol, fromRow) @@ -189,6 +202,8 @@ function obj.bitblt(dst, col, row, width, height, src, fromCol, fromRow) if not buf then return nil end + local cost = determineBitbltBudgetCost(buf, "screen") + if not machine.consumeCallBudget(cost) then return end buf.dirty = false width, height = math.min(buf.width, width), math.min(buf.height, height) component.cecinvoke(bindaddress, "bitblt", buf, col, row, width, height, fromCol, fromRow) @@ -473,7 +488,7 @@ function obj.get(x, y) if activeBufferIdx == 0 then return component.cecinvoke(bindaddress, "get", x, y) else - return bufferGet(buffers[activeBufferIdx], x, y) + return bufferGet(buffers[activeBufferIdx], x, y), nil, nil end end @@ -499,6 +514,7 @@ function obj.set(x, y, value, vertical) bufferSet(buffers[activeBufferIdx], x+i-1, y, ch) end end + return true end end diff --git a/src/component/screen_sdl2.lua b/src/component/screen_sdl2.lua index 3916cc3..e10af08 100644 --- a/src/component/screen_sdl2.lua +++ b/src/component/screen_sdl2.lua @@ -516,11 +516,9 @@ function cec.bitblt(buf, col, row, w, h, fromCol, fromRow) if dx >= 1 and dx <= width and dy >= 1 and dy <= height then srcfgc = fg srcbgc = bg - io.stdout:write(char) setPos(dx, dy, utf8.byte(char), fg, bg) end end - print("") end srcfgc = oldFg srcbgc = oldBg From 3e36f59038af6bb05609a5a0e901c065d8cc5b3d Mon Sep 17 00:00:00 2001 From: zenith391 Date: Sat, 20 Jun 2020 11:15:23 +0200 Subject: [PATCH 16/16] Fix bitblt cost --- src/component/gpu.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/component/gpu.lua b/src/component/gpu.lua index d1bcf4a..3bb42a9 100644 --- a/src/component/gpu.lua +++ b/src/component/gpu.lua @@ -16,7 +16,7 @@ local setPaletteColorCosts = {1/2, 1/8, 1/16} local setCosts = {1/64, 1/128, 1/256} local copyCosts = {1/16, 1/32, 1/64} local fillCosts = {1/32, 1/64, 1/128} -local bitbltCost = 0.5 +local bitbltCost = 0.5 * math.pow(2, maxtier) -- gpu component local mai = {}