Skip to content

Add EMU_SLOW_FACTOR environment variable to enable slowing down emulator #128

Open
donno2048 wants to merge 7 commits intodmsc:masterfrom
donno2048:patch-3
Open

Add EMU_SLOW_FACTOR environment variable to enable slowing down emulator #128
donno2048 wants to merge 7 commits intodmsc:masterfrom
donno2048:patch-3

Conversation

@donno2048
Copy link
Copy Markdown
Contributor

As proposed here #127

@donno2048 donno2048 marked this pull request as ready for review February 25, 2026 22:31
@donno2048 donno2048 mentioned this pull request Feb 25, 2026
@johnsonjh
Copy link
Copy Markdown
Collaborator

I might have some suggestions to make this more portable, I'll try to follow up soon.

@dmsc
Copy link
Copy Markdown
Owner

dmsc commented Mar 16, 2026

Hi!

I'm in favor of adding some means of slowing down the emulator for use cases where speed is important.

But, IMHO, this is not the correct approach - it uses a lot of CPU and is not precise.

Instead of "nanoseconds per instruction", the user should set the variable to "instructions per millisecond", for example with "EMU_CPU_SPEED=1000" for 1000000 instructions per second.

Then, in the emulator core, you set a (static) variable with the target time, this is the current monotonic time plus one millisecond. After EMU_CPU_SPEED instructions, you sleep if the current monotonic time is less than the target time, then advance the target time by 1 millisecond.

In pseudocode:

    // in main:
    target_cpu_time = get_monotonic_time() + 1ms;
    executed_ins_count = 0;
    while(1)
    {
        exit_cpu = 0;
        execute();
        emulator_update();
    }

   // in cpu.c:
   for(; !exit_cpu;)  {
        if(slowdown_cpu_speed)   {
            if(executed_ins_count++ >= slowdown_cpu_speed) {
                executed_ins_count -= slowdow_cpu_speed;
                while(target_cpu_time > get_monotonic_time())
                    usleep(1000);
                target_cpu_time += 1000;
            }
        }
        handle_irq();
        next_instruction();
   }

Then, you need to consider that REP will count as only only instruction, same as any IRQ or emulated DOS call.

Have Fun!

@donno2048
Copy link
Copy Markdown
Contributor Author

I don't have much spare time lately will try doing it next week, maybe even next month... 😅

@donno2048
Copy link
Copy Markdown
Contributor Author

Then, in the emulator core, you set a (static) variable with the target time, this is the current monotonic time plus one millisecond. After EMU_CPU_SPEED instructions, you sleep if the current monotonic time is less than the target time, then advance the target time by 1 millisecond

This means every chunk of intructions will run in a very little time period.

So for high values of EMU_CPU_SPEED it'll be a little jittery.

Since we deal with only one millisecond it might not be important, but we can maybe instead calculate the avrerage delay between instructions and just apply it to make the delay more uniform...

More importantly, there're situations where this approach can cause problems, for example, if there's a loop of 4 instructions and EMU_CPU_SPEED is a multiple of 4, and the first two instructions draw on the screen the third sleeps for a (very) little while a the fourth clears the screen, the expected behaviour will be to see an animation created by the two initial instructions but the read behaviour will be that because the 4 instructions are ran as a chunk the screen will always appear clear.

@donno2048
Copy link
Copy Markdown
Contributor Author

But, IMHO, this is not the correct approach - it uses a lot of CPU and is not precise.

You have to make a tradeoff, I might be wrong but I don't think on linux there's a way to make the OS suspend your process for an exact number of nanoseconds/millisecond/seconds/etc, so you either busy wait (like in #134) or be willing to suffer losing a few microseconds here and there.

@dmsc
Copy link
Copy Markdown
Owner

dmsc commented Mar 20, 2026

Hi!

You can think of the emulation as being "frame based", you can execute up to a video frame of instructions instantly, and then render the frame - the user will not notice any difference, as it is not possible to see more than one frame at a time.

This is the method used by all "game" emulators - you just emulate a frame of instructions (and video/audio/etc.) and then pass it to the video rendering.

In my example, instead of sleeping for one frame (16.6ms for 60HZ), IMHO, it is easier to simply sleep for 1ms, this will give the ability to absorb any jitter that the OS will introduce.

I will try to make a patch over the weekend and test this.

Have Fun!

@donno2048
Copy link
Copy Markdown
Contributor Author

@dmsc the weekend had passed, would it be helpful if I implement it on my own?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants