UE5 Soft Object Pointer not working as argument - c++

I’m trying to convert my Clue Management System in C++ and have made a Primary Data Asset to easily change the clue data. To make it as efficient and memory optimal as I can, I've decided to use soft object pointers to Data Assets that are loaded in as the clues are collected.
I'm trying to convert my Clue Management System in C++ and have made a Primary Data Asset to easily change the clue data. In my Clue Item code I have an interface call for attempting to interact with the Clue:
bool AClueBase::AttemptInteraction_Implementation(UInteractComponent* Interactor)
{
UE_LOG(LogClue, Display, TEXT("Clue Interacting"));
UClueManagerSubsystem* ClueManager = GetGameInstance()->GetSubsystem<UClueManagerSubsystem>();
if(!ClueManager) return false;
UE_LOG(LogClue, Display, TEXT("Clue Manager Valid"));
return ClueManager->CollectClue(ClueDataAsset);
}
This works fine, with the Clue Data Asset being a UPROPERTY:
UPROPERTY(BlueprintReadOnly, EditInstanceOnly) TSoftObjectPtr<UPrimaryDataAsset_Clue> ClueDataAsset;
The Function it is trying to call on the Clue Manager Subsystem is:
bool UClueManagerSubsystem::CollectClue(TSoftObjectPtr<UPrimaryDataAsset_Clue> Clue)
{
if(!Clue) return false;
UE_LOG(LogClue, Display, TEXT("Collecting Clue: %s"), *Clue->GetClueName());
FAreaClues AreaClues;
// If the Map has the Enum already, then we need to save a reference to the current value
if(CollectedClues.Contains(Clue->GetClueLocation()))
{
// If the Map has the Enum already, then we need to save a reference to the current value
AreaClues = CollectedClues[Clue->GetClueLocation()];
UE_LOG(LogClue, Display, TEXT("Clue Location already in Map"));
}
if(AreaClues.CollectedClues.Contains(Clue)) return false;
UE_LOG(LogClue, Display, TEXT("Clue doesn't already exist in Map"), *Clue->GetClueName());
AreaClues.CollectedClues.Add(Clue, true);
CollectedClues.Add(Clue->GetClueLocation(), AreaClues);
UE_LOG(LogClue, Display, TEXT("Clue Successfully Added"), *Clue->GetClueName());
OnCollectedClue.Broadcast(Clue);
return true;
}
It doesn't get past the first guard clause in this function, so the soft object pointer is a nullptr apparently. I am new to C++ with Soft Object Pointers so I might be entirely misusing them, so any correction would be greatly appreciated :)

Related

c++ best way to realise global switches/flags to control program behaviour without tying the classes to a common point

Let me elaborate on the title:
I want to implement a system that would allow me to enable/disable/modify the general behavior of my program. Here are some examples:
I could switch off and on logging
I could change if my graphing program should use floating or pixel coordinates
I could change if my calculations should be based upon some method or some other method
I could enable/disable certain aspects like maybe a extension api
I could enable/disable some basic integrated profiler (if I had one)
These are some made-up examples.
Now I want to know what the most common solution for this sort of thing is.
I could imagine this working with some sort of singelton class that gets instanced globally or in some other globally available object. Another thing that would be possible would be just constexpr or other variables floating around in a namespace, again globally.
However doing something like that, globally, feels like bad practise.
second part of the question
This might sound like I cant decide what I want, but I want a way to modify all these switches/flags or whatever they are actually called in a single location, without tying any of my classes to it. I don't know if this is possible however.
Why don't I want to do that? Well I like to make my classes somewhat reusable and I don't like tying classes together, unless its required by the DRY principle and or inheritance. I basically couldn't get rid of the flags without modifying the possible hundreds of classes that used them.
What I have tried in the past
Having it all as compiler defines. This worked reasonably well, however I didnt like that I couldnt make it so if the flag file was gone there were some sort of default settings that would make the classes themselves still operational and changeable (through these default values)
Having it as a class and instancing it globally (system class). Worked ok, however I didnt like instancing anything globally. Also same problem as above
Instancing the system class locally and passing it to the classes on construction. This was kinda cool, since I could make multiple instruction sets. However at the same time that kinda ruined the point since it would lead to things that needed to have one flag set the same to have them set differently and therefore failing to properly work together. Also passing it on every construction was a pain.
A static class. This one worked ok for the longest time, however there is still the problem when there are missing dependencies.
Summary
Basically I am looking for a way to have a single "place" where I can mess with some values (bools, floats etc.) and that will change the behaviour of all classes using them for whatever, where said values either overwrite default values or get replaced by default values if said "place" isnt defined.
If a Singleton class does not work for you , maybe using a DI container may fit in your third approach? It may help with the construction and make the code more testable.
There are some DI frameworks for c++, like https://github.com/google/fruit/wiki or https://github.com/boost-experimental/di which you can use.
If you decide to use switch/flags, pay attention for "cyclometric complexity".
If you do not change the skeleton of your algorithm but only his behaviour according to the objets in parameter, have a look at "template design pattern". This method allow you to define a generic algorithm and specify particular step for a particular situation.
Here's an approach I found useful; I don't know if it's what you're looking for, but maybe it will give you some ideas.
First, I created a BehaviorFlags.h file that declares the following function:
// Returns true iff the given feature/behavior flag was specified for us to use
bool IsBehaviorFlagEnabled(const char * flagName);
The idea being that any code in any of your classes could call this function to find out if a particular behavior should be enabled or not. For example, you might put this code at the top of your ExtensionsAPI.cpp file:
#include "BehaviorFlags.h"
static const enableExtensionAPI = IsBehaviorFlagEnabled("enable_extensions_api");
[...]
void DoTheExtensionsAPIStuff()
{
if (enableExtensionsAPI == false) return;
[... otherwise do the extensions API stuff ...]
}
Note that the IsBehaviorFlagEnabled() call is only executed once at program startup, for best run-time efficiency; but you also have the option of calling IsBehaviorFlagEnabled() on every call to DoTheExtensionsAPIStuff(), if run-time efficiency is less important that being able to change your program's behavior without having to restart your program.
As far as how the IsBehaviorFlagEnabled() function itself is implemented, it looks something like this (simplified version for demonstration purposes):
bool IsBehaviorFlagEnabled(const char * fileName)
{
// Note: a real implementation would find the user's home directory
// using the proper API and not just rely on ~ to expand to the home-dir path
std::string filePath = "~/MyProgram_Settings/";
filePath += fileName;
FILE * fpIn = fopen(filePath.c_str(), "r"); // i.e. does the file exist?
bool ret = (fpIn != NULL);
fclose(fpIn);
return ret;
}
The idea being that if you want to change your program's behavior, you can do so by creating a file (or folder) in the ~/MyProgram_Settings directory with the appropriate name. E.g. if you want to enable your Extensions API, you could just do a
touch ~/MyProgram_Settings/enable_extensions_api
... and then re-start your program, and now IsBehaviorFlagEnabled("enable_extensions_api") returns true and so your Extensions API is enabled.
The benefits I see of doing it this way (as opposed to parsing a .ini file at startup or something like that) are:
There's no need to modify any "central header file" or "registry file" every time you add a new behavior-flag.
You don't have to put a ParseINIFile() function at the top of main() in order for your flags-functionality to work correctly.
You don't have to use a text editor or memorize a .ini syntax to change the program's behavior
In a pinch (e.g. no shell access) you can create/remove settings simply using the "New Folder" and "Delete" functionality of the desktop's window manager.
The settings are persistent across runs of the program (i.e. no need to specify the same command line arguments every time)
The settings are persistent across reboots of the computer
The flags can be easily modified by a script (via e.g. touch ~/MyProgram_Settings/blah or rm -f ~/MyProgram_Settings/blah) -- much easier than getting a shell script to correctly modify a .ini file
If you have code in multiple different .cpp files that needs to be controlled by the same flag-file, you can just call IsBehaviorFlagEnabled("that_file") from each of them; no need to have every call site refer to the same global boolean variable if you don't want them to.
Extra credit: If you're using a bug-tracker and therefore have bug/feature ticket numbers assigned to various issues, you can creep the elegance a little bit further by also adding a class like this one:
/** This class encapsulates a feature that can be selectively disabled/enabled by putting an
* "enable_behavior_xxxx" or "disable_behavior_xxxx" file into the ~/MyProgram_Settings folder.
*/
class ConditionalBehavior
{
public:
/** Constructor.
* #param bugNumber Bug-Tracker ID number associated with this bug/feature.
* #param defaultState If true, this beheavior will be enabled by default (i.e. if no corresponding
* file exists in ~/MyProgram_Settings). If false, it will be disabled by default.
* #param switchAtVersion If specified, this feature's default-enabled state will be inverted if
* GetMyProgramVersion() returns any version number greater than this.
*/
ConditionalBehavior(int bugNumber, bool defaultState, int switchAtVersion = -1)
{
if ((switchAtVersion >= 0)&&(GetMyProgramVersion() >= switchAtVersion)) _enabled = !_enabled;
std::string fn = defaultState ? "disable" : "enable";
fn += "_behavior_";
fn += to_string(bugNumber);
if ((IsBehaviorFlagEnabled(fn))
||(IsBehaviorFlagEnabled("enable_everything")))
{
_enabled = !_enabled;
printf("Note: %s Behavior #%i\n", _enabled?"Enabling":"Disabling", bugNumber);
}
}
/** Returns true iff this feature should be enabled. */
bool IsEnabled() const {return _enabled;}
private:
bool _enabled;
};
Then, in your ExtensionsAPI.cpp file, you might have something like this:
// Extensions API feature is tracker #4321; disabled by default for now
// but you can try it out via "touch ~/MyProgram_Settings/enable_feature_4321"
static const ConditionalBehavior _feature4321(4321, false);
// Also tracker #4222 is now enabled-by-default, but you can disable
// it manually via "touch ~/MyProgram_Settings/disable_feature_4222"
static const ConditionalBehavior _feature4222(4222, true);
[...]
void DoTheExtensionsAPIStuff()
{
if (_feature4321.IsEnabled() == false) return;
[... otherwise do the extensions API stuff ...]
}
... or if you know that you are planning to make your Extensions API enabled-by-default starting with version 4500 of your program, you can set it so that Extensions API will be enabled-by-default only if GetMyProgramVersion() returns 4500 or greater:
static ConditionalBehavior _feature4321(4321, false, 4500);
[...]
... also, if you wanted to get more elaborate, the API could be extended so that IsBehaviorFlagEnabled() can optionally return a string to the caller containing the contents of the file it found (if any), so that you could do shell commands like:
echo "opengl" > ~/MyProgram_Settings/graphics_renderer
... to tell your program to use OpenGL for its 3D graphics, or etc:
// In Renderer.cpp
std::string rendererType;
if (IsDebugFlagEnabled("graphics_renderer", &rendererType))
{
printf("The user wants me to use [%s] for rendering 3D graphics!\n", rendererType.c_str());
}
else printf("The user didn't specify what renderer to use.\n");

Adding to a list from another class

I am trying to add the instance of an object that I click on to a list on my control object. However when I do so it says that the reference is not set to an instance of an object. The code I have to instantiate the list on the control object is:
public List<Transform> selected = new List<Transform>();
And I tried to add to it to that list using this code attached to the unit:
if (!selected)
{
// Set selected state
selected = true;
// Add to Selected List
control.GetComponent<ForwardCommandScript>().selected.Add(this.transform);
// Set material colour brighter
oldColour = gameObject.renderer.material.color;
newColour = oldColour + new Color(0.2f, 0.2f, 0.2f);
gameObject.renderer.material.color = newColour;
}
I have tried with transform as well. Later I will try to remove it by finding a reference id that was set when the unit is instantiated so should I try to add the script instead of the object if I need to find its variables and then delete the game object attached to the script. I have tried with the GameObject, transform and the class. I wanted to use the class so I can easily access the variables. I have posted this on unity answers and forums but no one replied in the week it was up and I don't like reposting the same stuff on the same site.
Cheers, Scobbo
Your error NullReferenceException: Object reference not set to an instance of an object states that something in the associated line is null. Since the error message doesn't state which part is null, you have to split your code up and check which part is failing.
I'm not sure how you split it up, but try it this way:
var script = control.GetComponent<ForwardCommandScript>();
if (script == null) Debug.Log("script not found");
if (script.selected == null) Debug.Log("selected is null");
script.selected.Add(this.transform);
Now you should get one of the two messages in your debug log before the exception raises. Either the script was not found and you have to check if it is correctly assigned to the game object and if control is the correct game object, or selected is null which should not happen if you initialized it like you posted...
Thanks for adding the complete Error Message :)
you need to replace
control.GetComponent<ForwardCommandScript>().selected.Add(this.transform);
with
control.GetComponent<ForwardCommandScript>().selected.Add(transform);
because
this
is a reference to the script and not the GameObject. you could also use gameObject.transform which transform is just an abbreviation for

Google Maps Android API v2 getVisibleRegion() returns 0

I am using the new Google Maps Android API v2 and need to detect latlng coordinates for the corners of my screen view. I use mMap.getProjection().getVisibleRegion() to do this, which works fine the first time I load the program, but if I leave the program and then re-open it (either via the back button or the home button) getVisibleRegion() returns latlng coordinates that are all (0.0, 0.0). I have a workaround where I save the VisibleRegion object as a global in the application class when I first open the program, but this seems like a bad idea. Anyone understand why the latlng coordinates go to zero (but not null) when re-opening? Thanks!
Edit: sometimes initial load gives a (0.0, 0.0) Visible Region so my workaround is not viable. Using getProjection().fromScreenLocation(point) also returns 0.
Okay, I think I solved it: I was guessing right: The map was not visible and therefore there is also no visibleRegion (in other words a region from 0,0,0,0 to 0,0,0,0). Similar to this question
Android Google Maps API v2 calling getProjection from onResume
I attached an onCameraChanged-Listener to my map. Everytime the map is moved (or the camera is available => There is a visibleRegion) this method is called.
private void initMap() {
mMap = getMap();
if (mMap != null) {
try {
mMap.setLocationSource(this);
mMap.setOnMarkerClickListener(this);
mMap.setOnCameraChangeListener(new OnCameraChangedListener() {
#Override
public void onCameraChange(CameraPosition cameraPosition) {
Log.d(TAG, "onCameraChange");
updateMapItems();
}
}
MapsInitializer.initialize(getActivity());
...
}
}
}
This listener makes sure that you have a visibleRegion.
UPDATE: Since the new update of the Google Maps API v2, there is a callback onMapReady(). I did not use it yet, but it seems to serve exactly the purpose of this question: https://developer.android.com/reference/com/google/android/gms/maps/OnMapReadyCallback.html

getElementsByTagName returns 0-length list when called from didFinishLoad delegate

I'm using the Chromium port of WebKit on Windows and I'm trying to retrieve a list of all of the images in my document. I figured the best way to do this was to implement WebKit::WebFrameClient::didFinishLoading like so:
WebNodeList list = document->getElementsByTagName(L"img");
for (size_t i = 0; i < list.length(); ++i) {
// Manipulate images here...
}
However, when this delegate fires, list.length() returns 0. The only times I've seen it return a list of non-zero length is when I substitute "body" or "head" for "img". Strangely enough, if I call getElementsByTagName(L"img") outside of the delegate, it works correctly.
I'm guessing that the DOM isn't fully loaded when didFinishLoading is called, but that would seem to contradict the delegate's name. Does anyone know what I may be missing here?
It turns out that the mistake was purely on my side. I was caching a pointer to the DOM document in my frame wrapper. Of course, since a frame can outlive a DOM document, I ended up referencing an out-of-date document once I loaded a new page.

Use a MSHFlexGrid with non-database data and still define the shape

I am trying to use the Microsoft Hierarchical FlexGrid (MSHFlexGrid) in a Visual C++ (VS 2005). I have the grid shown, and I can manually add data to the individual cells. However, according to online documentation I've read, I should be able to show the hierarchical nature of the data (hence MSHFlexGrid instead of MSFlexGrid) by defining the SHAPE as the RecordSource. I can do that fine (by using the put_RecordSource method of the grid object), however I'm at a loss as to how to add the actual data.
I've read that the best way to do this is to use an ADO Data Control (ie ADODC) component and bind it as the DataSource for the Grid. You can then specify "provider=msdatashape;data provider=none;" as the provider of the DataControl and fill it with data. If I were doing SQL, I'd specify my SELECT query as the RecordSource, then call Refresh() and let the control load the data.
However, my data is in custom objects. I know what needs to be displayed, I'm just at a loss as to the best way to insert the data into the FlexGrid and still use the built in features of the control. I'm open to any suggestions, but I need to keep the data local (ie no JET, Access, etc).
Here's some code:
In header:
....
// Variable to control the Flex Grid component
CMshflexgrid1 m_grid; //generated by wizard from the MSHFlexGrid component
// to control the data source hierarchical information
CAdodc1 m_adodc1;
....
In cpp:
....
BOOL MyDialogClass::OnInitDialog()
{
CDialog::OnInitDialog();
m_grid.Clear();
CString strCn = "provider=msdatashape;data provider=none;";
m_adodc1.put_ConnectionString(strCn);
CString BackupOfRecordSource = "";
BackupOfRecordSource = m_adodc1.get_RecordSource();
//CString strShape = "SHAPE APPEND new adInteger As PID, New adVarChar(10) As StudentName, ((SHAPE APPEND new adInteger As ChID, New adVarChar(10) As Course, ((SHAPE APPEND new adInteger As GrndChID, New adBSTR As Description) RELATE ChID TO GrndChID) As GrandChild) RELATE PID TO ChID) AS Child";
CString strShape = "SHAPE APPEND new adInteger As PID, New adVarChar(10) As StudentName";
m_adodc1.put_RecordSource(strShape);
m_adodc1.Refresh();
m_grid.Refresh();
BackupOfRecordSource = m_adodc1.get_RecordSource(); //returns the strShape that I just put in
//ADD RECORDS HERE! HOW?
return TRUE;
}
The sample talked about building an ADODB.Recordset and use it as the data source of ADODC. The code you give is building a SQL and use it as the data source of ADODC. I don't think you can replace an ADODB.Recordset with a string.