Skip to content

Add EMU_SLOW_FACTOR environment variable to enable slowing down emulator - #128

Closed
donno2048 wants to merge 7 commits into
dmsc:masterfrom
donno2048:patch-3
Closed

Add EMU_SLOW_FACTOR environment variable to enable slowing down emulator #128
donno2048 wants to merge 7 commits into
dmsc: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
Comment thread README.md Outdated
@donno2048 donno2048 mentioned this pull request Mar 10, 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

dmsc commented Mar 16, 2026

Copy link
Copy Markdown
Owner

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

dmsc commented Mar 20, 2026

Copy link
Copy Markdown
Owner

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?

@donno2048

Copy link
Copy Markdown
Contributor Author

@dmsc ?

@dmsc

dmsc commented Apr 24, 2026

Copy link
Copy Markdown
Owner

Hi!

@dmsc ?

Sorry, I was out for a while. In the meantime, I have an implementation and will begin to push it now.

Thank you for the reminder :)

@dmsc

dmsc commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Merged my implementation, please take a look. Closing this, thanks!!

@dmsc dmsc closed this Apr 25, 2026
@donno2048
donno2048 deleted the patch-3 branch April 26, 2026 07:30
@donno2048

Copy link
Copy Markdown
Contributor Author

Merged my implementation, please take a look. Closing this, thanks!!

Looks good, closing #134 too, couldn't help but notice you haven't mentioned EMU2_CPU_SPEED environment variable in the README though

@dmsc

dmsc commented Apr 26, 2026

Copy link
Copy Markdown
Owner

Thank you, added a note and pushed.

Have Fun!

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