Long time no see and Input Capture

The Partridge Family were neither partridges nor a family. Discuss.
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Long time no see and Input Capture

Post by albinopapa » November 12th, 2018, 9:53 pm

Code: Select all

class Input
{
public:
	Input() = default;
	template<typename T>
	Input( T&& _value )
		:
		data( std::move( _value ) )
	{}
	void ReadData( std::ifstream& is )
	{
		auto read_data = [ & ]( auto& _data )
		{
			_data.ReadData( is );
		};

		std::visit( read_data, data );
	}
	void SaveData( std::ostream& os )const
	{
		auto write_data = [ & ]( const auto& _data )
		{
			_data.SaveData( os );
		};

		std::visit( write_data, data );
	}

	template<typename T, typename IfSame, typename IfNotSame>
	void ConditionalCall( IfSame&& _ifsame, IfNotSame&& _ifnotsame )
	{
		auto call = [ & ]( auto& _data )
		{
			using type = std::decay_t<decltype( _data )>;
			if constexpr( std::is_same_v<type, T> )
			{
				_ifsame(_data);
			}
			else
			{
				_ifnotsame();
			}
		};

		std::visit( call, data );
	}
private:
	InputData data;
};
How about some polymorphic behavior? The ConditionalCall is used like this:

Code: Select all

Input* ptr = recordList.GetBack();
if (ptr)
{
	ptr->ConditionalCall<DelayData>(
		[ delay ]( DelayData& _data )
	{
		_data.AddDelay( delay );
	}, [ delay ]()
	{
		recordList.AddEventToRecord<DelayData>( delay );
	} );
}
SIDE RANT: I would have liked to see the use of std::optional for the recordList.GetBack() et al, but std::optional doesn't support references, so it wouldn't have made sense to make a copy of the DelayData had there been something to return from .GetBack().

Had to make a few changes to get it to accept the single Input interface instead of using the overloaded visitor structs. For common cases like serializing and deserializing, the interface feels like a polymorphic type, but in special cases like DelayData::AddDelay, it feels foreign. I suppose you could also just put the AddDelay in the Input class:

Code: Select all

bool AddDelay( float _delay )
{
	auto add_delay = [_delay]( auto& _data )
	{
		using type = decay_t<decltype(_data)>;
		if constexpr( std::is_same_v<type, DelayData> )
		{
			_data.AddDelay( _delay );
			return true;
		}
		return false;
	};

	return std::visit( add_delay, data );
}
// Then this code becomes
Input* ptr = recordList.GetBack();
if (ptr)
{
	if( !ptr->AddDelay( delay ) )
		recordList.AddEventToRecord<DelayData>( delay );
}
After doing that, I kind of like how clean it makes the usage of Input objects, and it would have been no different than having a virtual function in a base class anyway.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Long time no see and Input Capture

Post by albinopapa » November 12th, 2018, 9:58 pm

BTW, Ctrl + Esc to quit is a terrible key combo, it kept bringing up my start menu, so can't exit unless I use the VS Stop Debugging command. Perhaps there are some key combos you should override lol.

What I mean by caching is you press a key, store that key press so that on the next iteration of the message pump, you get the next key and can see if the cached key state was an Alt or Ctrl.

You are wanting two keys, but as far as I understand, each iteration through the message queue only returns events ( key press/release ) one at a time.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Slidy
Posts: 80
Joined: September 9th, 2017, 1:19 pm

Re: Long time no see and Input Capture

Post by Slidy » November 12th, 2018, 11:08 pm

Alt+Tab and Ctrl+Esc are both Windows shortcuts. Alt+Tab switches window and Ctrl+Esc brings up the start menu. Perhaps that's why it won't allow you to use them?

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Long time no see and Input Capture

Post by albinopapa » November 13th, 2018, 12:32 am

I'd stick with Alt + F4 for quitting.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

cameron
Posts: 794
Joined: June 26th, 2012, 5:38 pm
Location: USA

Re: Long time no see and Input Capture

Post by cameron » November 13th, 2018, 7:55 pm

Thanks for the suggestions.

Interesting ideas there papa. Originally I had it in a polymorphic system with virtual funcs in a base class. What I didnt like is the strange cases with DelayData and needing to store vector<unique_ptr<InputData>>. What changes did you make to the visitor structs(?) to get that single interface there? That conditionalcall is fancy as well; tho I'm not sure it saves any code.

Right now, I'm not caching any results of key presses (this is something I plan on doing), right now I'm using getasynckeystate to check the states of the other keys( see CheckKey.cpp ).

As far as the key combos go, I agree that they may not be "good" combinations. However, that is not the point... The program should be able to record and simulate any input (think remote desktop and alt + tab or ctrl + alt + delete didn't work). The issue, in this case, is not simulation, but the fact that the input is not being registered by WM_INPUT for certain key combinations.

On a side note:
I made a bunch of changes to try and cleanup windows.cpp (window class, styles struct, etc...)
Computer too slow? Consider running a VM on your toaster.

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Long time no see and Input Capture

Post by albinopapa » November 13th, 2018, 10:55 pm

Here's the commit history I came up with...it's kind of long
  1. Remove experimental from the std::filesystem namespace ( File.h )
  2. Replace placement new calls with Function initializer in constructors ( Function.h )
  3. Remove unneeded inline from Function::operator() ( Function.h )
  4. Replace const std::vector<KeyEntry> reference with pass by value in IgnoreKeys::SetKeys ( IgnoreKeys.h )
  5. Remove empty checks from both IgnoreKeys::SetKey functions ( IgnoreKeys.h )
  6. Move assign ignoreList parameter to this->ignoreList instead of copy ( IgnoreKeys.h )
  7. Move code from visitor structs operator() to related named functions in each InputData struct, such as ReadData and SaveData ( InputData.h )
  8. Give each InputData struct a constructor that takes a ifstream& for initializing the struct members during creation ( InputData.h )
  9. Move implementation code from visitor structs to applicable input data structs ( InputData.cpp )
  10. Remove InputHandler move constructor and assignment operator, all members are moveable without needing to manually move them. ( InputHandler.h and .cpp )
  11. Declare InputHandler default constructor as = default ( InputHandler.h )
  12. In-class initialize bool recording = false ( InputHandler.h )
  13. Remove InputHandler destructor ( InputHandler.h and .cpp )
  14. TODO: Implement SimulateVisitor behavior
  15. Move switch block to a lambda that returns an Input object, returned object gets passed directly to
  16. InputHandler::Add( InputHandler.cpp )
  17. Create InputHandler::Add overload that takes an Input object ( InputHandler.h )
  18. Remove separate declare and open stream, combine to use stream constructors ( InputHandler.cpp )
  19. Write entire toggleKeys vector in one call to stream.write instead of each TCHAR in a loop ( InputHandler.cpp )
  20. Use const where possible ( InputHandler.cpp )
  21. Change InputHandler::GetBack to return Input* instead of InputData*
  22. Make separate const and non-const versions to avoid casting away const ( InputHandler.h and .cpp )
  23. Copy entire toggleKeys vector into stream plus '+' - make temporary std::string from stream.str() - call str.pop_back() on temproary to remove the ending '+' - return temporary std::string rely on guaranteed RVO and copy/move ellision( InputHandler.cpp )
  24. Change InputHandler::Load/Save signature to use const std::string& instead of const char* since it's never called using a string literal ( InputHandler.h and .cpp )
  25. Make separate const and non-const versions of RecordList::GetBack to avoid casting away const
  26. Replace raw for loop with std::find_if STL algorithm in RecorList::SelectRecord ( RecordList.cpp )
  27. Modify the for loop in RecordList::Initialize to be a ranged for loop - use returned reference from records.emplace_back to call InputHandler::Load and move assign the filename string ( RecordList.cpp )
  28. Use std::find_if in RecordList::FindRecord ( RecordList.cpp )
  29. Replace unique_ptr with vector in all SimInp functions ( SimInp.cpp )
  30. Make templated versions of KeyCombo and KeyComboSC to accept std::vector or std::initializer_list, thus avoiding duplicate code in the SimInp::KeyCombo and SimInp::KeyComboSC functions ( SimInp.cpp )
If you'd like, I can make a pull request so you can see the changes instead of the descriptions of changes.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Long time no see and Input Capture

Post by albinopapa » November 13th, 2018, 11:14 pm

I didn't quite go through to figure out what the SimulateVisitor did, so I kind of just commented it out.

Here's an example of the DelayData struct:

Code: Select all

// InputData.h
struct DelayData
{
	static constexpr int uuid = 0;
	DelayData( std::ifstream& is );
	DelayData(DWORD delayMilli = 0);

	void AddDelay(DWORD delay);
	void ReadData( std::ifstream& is );
	void SaveData( std::ostream& os )const;

	DWORD delayMilli = 0;
};

// InputData.cpp
DelayData::DelayData( std::ifstream & is )
{
	ReadData( is );
}
DelayData::DelayData(DWORD delayMilli)
	:
	delayMilli(delayMilli)
{}
void DelayData::AddDelay(DWORD delay)
{
	delayMilli += delay;
}
void DelayData::ReadData( std::ifstream & is )
{
	is.read( ( char* )&delayMilli, sizeof( DWORD ) );
}
void DelayData::SaveData( std::ostream& os )const
{
	os.write( ( const char* )&uuid, sizeof( int ) );
	os.write( ( const char* )&delayMilli, sizeof( DWORD ) );
}
So, after looking at the SimulateVisitor functions, you could probably have functions in each of the input data structs called: void Simulate()const;

Code: Select all

void DelayData::Simulate() const
{
	std::this_thread::sleep_for( std::chrono::milliseconds( delayMilli ) );
}
Input::Simulate interface function

Code: Select all

void Input::Simulate()const
{
	auto simulate = []( const auto& _data )
	{
		_data.Simulate();
	};

	std::visit( simulate, data );
}
Then, just call: input.Simulate();

Code: Select all

void InputHandler::Simulate()
{
	StopRecording();
	for( const auto& input : inputs )
		input.Simulate();
}
A lot of the commit log doesn't have to do with the variant stuff, but thought I'd share my opinions anyway.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

cameron
Posts: 794
Joined: June 26th, 2012, 5:38 pm
Location: USA

Re: Long time no see and Input Capture

Post by cameron » November 14th, 2018, 12:06 am

Would be great if you made a pull request so I can look over some of the details.

Side note:
Deciding if I want to use exceptions or stick to msgbox for errors right now. I've never been a big fan of exceptions but they can be more useful than other forms of error-management in certain situations.
Computer too slow? Consider running a VM on your toaster.

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Long time no see and Input Capture

Post by albinopapa » November 14th, 2018, 1:25 am

As requested: Pull-Request

As for exceptions, currently exceptions thrown from threads calls std::terminate, so no destructors are called if thrown, so if you plan on creating resources from threads be cautious. If exceptions are only ever thrown from main thread, then you should be able to catch and use MessageBox to display the error, though I'm not sure about handling shutting down thread s at that point.

I would imagine handling exceptions in a threading environment would get kind of messy, having local try catch blocks in functions that COULD throw, release resources, end the thread, then maybe rethrow to an outer try/catch block.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

cameron
Posts: 794
Joined: June 26th, 2012, 5:38 pm
Location: USA

Re: Long time no see and Input Capture

Post by cameron » November 14th, 2018, 3:16 am

Thanks, I'll probably be fooling around with some of this for the next little while and see what I like. But I have to agree it is cleaner to have the input class rather than a bunch of visitor structs. Pretty neat having that AddDelay function in there too to clean up that little bit of inconsistency/redundancy. I didn't really piece it all together until I say the full code.

Good to know that exceptions aren't gonna work out well in a multithreaded environment. Guess ill stick to msgbox for now.
Computer too slow? Consider running a VM on your toaster.

Post Reply